<!DOCTYPE html>
<html lang="en-US" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>RocketMQ概述 | VitePress</title>
    <meta name="description" content="A VitePress site">
    <link rel="preload stylesheet" href="/notebook/assets/style.3dbfd0c2.css" as="style">
    
    <script type="module" src="/notebook/assets/app.8aaa4cbe.js"></script>
    <link rel="preload" href="/notebook/assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="/notebook/assets/chunks/framework.1336c4e5.js">
    <link rel="modulepreload" href="/notebook/assets/chunks/theme.20cddc0c.js">
    <link rel="modulepreload" href="/notebook/assets/消息中间件_RocketMQ.md.d441da85.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-255ec12d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-ae3e3f51></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-ae3e3f51> Skip to content </a><!--]--><!----><header class="VPNav" data-v-255ec12d data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-0937f67c><div class="container" data-v-0937f67c><div class="title" data-v-0937f67c><div class="VPNavBarTitle has-sidebar" data-v-0937f67c data-v-86d1bed8><a class="title" href="/notebook/" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="/notebook/Vue.png" alt data-v-8426fc1a><!--]--><!--[-->任硕的文档<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-0937f67c><div class="curtain" data-v-0937f67c></div><div class="content-body" data-v-0937f67c><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-0937f67c><!--[--><!----><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-0937f67c data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>Java学前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/HTML+JS.html" data-v-2f2cfafc><!--[-->HTML+JS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/CSS.html" data-v-2f2cfafc><!--[-->CSS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue2+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue2+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue3+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue3+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/React.html" data-v-2f2cfafc><!--[-->React<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>软件测试</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->测试基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95.html" data-v-2f2cfafc><!--[-->压力测试<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>多线程</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-2f2cfafc><!--[-->基础篇<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%B9%B6%E5%8F%91%E5%AE%8C%E5%96%84.html" data-v-2f2cfafc><!--[-->进阶篇<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>开发工具</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/Chrome.html" data-v-2f2cfafc><!--[-->Chrome<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->IDEA基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E6%8F%92%E4%BB%B6.html" data-v-2f2cfafc><!--[-->IDEA插件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/VS%20Code.html" data-v-2f2cfafc><!--[-->VS Code<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>消息中间件</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ.html" data-v-2f2cfafc><!--[-->RabbitMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link active" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ.html" data-v-2f2cfafc><!--[-->RocketMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka.html" data-v-2f2cfafc><!--[-->Kafka<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Canal.html" data-v-2f2cfafc><!--[-->Canal<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-0937f67c data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-0937f67c data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-0937f67c data-v-40855f84 data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-a7b5672a><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-a7b5672a><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-0937f67c data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-255ec12d data-v-5cfd5582><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-5cfd5582><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-5cfd5582><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-5cfd5582>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-5cfd5582 data-v-18201f51><button data-v-18201f51>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-255ec12d data-v-845b8fc6><div class="curtain" data-v-845b8fc6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-845b8fc6><span class="visually-hidden" id="sidebar-aria-label" data-v-845b8fc6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Java</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%9B%86%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java集合</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Linux</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Shell.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Shell脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实用脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E8%BD%AF%E4%BB%B6%E9%83%A8%E7%BD%B2.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>软件部署</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Nginx</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%AE%9E%E6%88%98%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实战篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E9%9D%A2%E8%AF%95%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>面试篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SSM</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Maven.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Maven</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Spring.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Spring</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringMVC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringMVC</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringBatch.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringBatch</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringBoot</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%BA%94%E7%94%A8%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>应用篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E8%BF%90%E7%BB%B4&amp;%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>运维&原理</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringCloud</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringCloud</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/Sentinel.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Sentinel</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringSecurity</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Mybatis & MybatisPlus</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/Mybatis.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Mybatis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/MybatisPlus.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MybatisPlus</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/JPA.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JPA</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Git & ChatGPT</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Git.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Git</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Github.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Github</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/ChatGPT.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ChatGPT</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Jenkins.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Jenkins</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Netty.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Netty</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>数据库</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MySQL</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL设计</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%90%E7%BB%B4.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL运维</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分库分表</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Redis</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis原理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis实战</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>本地缓存</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MongoDB</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E6%95%B4%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>ElasticSearch</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/1%E3%80%81ES%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/3%E3%80%81ES%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/influxdb.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>InfluxDB</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Neo4j</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>高并发 & 秒杀 & 分布式</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E5%88%86%E5%B8%83%E5%BC%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式理论</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式锁</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E7%A7%92%E6%9D%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>秒杀</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%8F%AF%E7%94%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高可用</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%B9%B6%E5%8F%91.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高并发</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>云原生</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/Docker.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Docker</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/K8S.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>K8S</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>可视化 & 监控</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%A7%E5%B1%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>可视化大屏</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/Zabbix.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Zabbix</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>学前端</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>HTML+CSS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/HTML%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>HTML基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/CSS%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>CSS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/%E7%BD%91%E9%A1%B5%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>网页进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>JS+TS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/TypeScript.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>TS基础</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>NodeJS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Vue</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E6%96%B0%E8%AF%AD%E6%B3%95.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3新语法</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue2/Vue2%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小程序</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/uniapp.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>uniapp</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>计算机基础</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>数据结构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>操作系统</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>设计模式</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>计算机网络</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/UML.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>UML</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95/LeetCode.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>LeetCode</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>项目实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>云尚办公</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E4%BA%91%E5%B0%9A%E5%8A%9E%E5%85%AC/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小兔鲜</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%871.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇1</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>地图</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>苍穹外卖</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>黑马头条</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E6%94%AF%E4%BB%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>支付</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目推荐</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0" data-v-845b8fc6 data-v-9b797284><!----><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/team.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>团队成员</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-255ec12d data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-3f215769 data-v-ff0f39c8><div class="content" data-v-ff0f39c8><div class="outline-marker" data-v-ff0f39c8></div><div class="outline-title" data-v-ff0f39c8>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-ff0f39c8><span class="visually-hidden" id="doc-outline-aria-label" data-v-ff0f39c8> Table of Contents for current page </span><ul class="root" data-v-ff0f39c8 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _notebook_%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6_RocketMQ" data-v-6b87e69f><div><h1 id="rocketmq概述" tabindex="-1">RocketMQ概述 <a class="header-anchor" href="#rocketmq概述" aria-label="Permalink to &quot;RocketMQ概述&quot;">​</a></h1><h2 id="mq概述" tabindex="-1">MQ概述 <a class="header-anchor" href="#mq概述" aria-label="Permalink to &quot;MQ概述&quot;">​</a></h2><h3 id="mq简介" tabindex="-1">MQ简介 <a class="header-anchor" href="#mq简介" aria-label="Permalink to &quot;MQ简介&quot;">​</a></h3><blockquote><p>MQ，Message Queue，是一种提供消息队列服务的中间件，也称为消息中间件，是一套提供了消息生 产、存储、消费全过程API的软件系统。消息即数据。一般消息的体量不会很大。</p></blockquote><h3 id="mq用途" tabindex="-1">MQ用途 <a class="header-anchor" href="#mq用途" aria-label="Permalink to &quot;MQ用途&quot;">​</a></h3><p>从网上可以查看到很多的关于MQ用途的叙述，但总结起来其实就以下三点。</p><h4 id="限流削峰" tabindex="-1">限流削峰 <a class="header-anchor" href="#限流削峰" aria-label="Permalink to &quot;限流削峰&quot;">​</a></h4><blockquote><p>MQ可以将系统的超量请求暂存其中，以便系统后期可以慢慢进行处理，从而避免了请求的丢失或系统 被压垮。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011926060.png" alt="image-20230601192614918" style="zoom:80%;"><h4 id="异步解耦" tabindex="-1">异步解耦 <a class="header-anchor" href="#异步解耦" aria-label="Permalink to &quot;异步解耦&quot;">​</a></h4><blockquote><p>上游系统对下游系统的调用若为同步调用，则会大大降低系统的吞吐量与并发度，且系统耦合度太高。 而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化，一般性做法就是，在这两 层间添加一个MQ层。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011926553.png" alt="image-20230601192651443" style="zoom:80%;"><h4 id="数据收集" tabindex="-1">数据收集 <a class="header-anchor" href="#数据收集" aria-label="Permalink to &quot;数据收集&quot;">​</a></h4><blockquote><p>分布式系统会产生海量级数据流，如：业务日志、监控数据、用户行为等。针对这些数据流进行实时或 批量采集汇总，然后对这些数据流进行大数据分析，这是当前互联网平台的必备技术。通过MQ完成此 类数据收集是最好的选择。</p></blockquote><h3 id="常见mq产品" tabindex="-1">常见MQ产品 <a class="header-anchor" href="#常见mq产品" aria-label="Permalink to &quot;常见MQ产品&quot;">​</a></h3><h4 id="activemq" tabindex="-1">ActiveMQ <a class="header-anchor" href="#activemq" aria-label="Permalink to &quot;ActiveMQ&quot;">​</a></h4><blockquote><p>ActiveMQ是使用Java语言开发一款MQ产品。早期很多公司与项目中都在使用。但现在的社区活跃度已 经很低。现在的项目中已经很少使用了。</p></blockquote><h4 id="rabbitmq" tabindex="-1">RabbitMQ <a class="header-anchor" href="#rabbitmq" aria-label="Permalink to &quot;RabbitMQ&quot;">​</a></h4><blockquote><p>RabbitMQ是使用ErLang语言开发的一款MQ产品。其吞吐量较Kafka与RocketMQ要低，且由于其不是 Java语言开发，所以公司内部对其实现定制化开发难度较大。</p></blockquote><h4 id="kafka" tabindex="-1">Kafka <a class="header-anchor" href="#kafka" aria-label="Permalink to &quot;Kafka&quot;">​</a></h4><blockquote><p>Kafka是使用Scala/Java语言开发的一款MQ产品。其最大的特点就是高吞吐率，常用于大数据领域的实 时计算、日志采集等场景。其没有遵循任何常见的MQ协议，而是使用自研协议。对于Spring Cloud Netflix，其仅支持RabbitMQ与Kafka。</p></blockquote><h4 id="rocketmq" tabindex="-1">RocketMQ <a class="header-anchor" href="#rocketmq" aria-label="Permalink to &quot;RocketMQ&quot;">​</a></h4><blockquote><p>RocketMQ是使用Java语言开发的一款MQ产品。经过数年阿里双11的考验，性能与稳定性非常高。其 没有遵循任何常见的MQ协议，而是使用自研协议。对于Spring Cloud Alibaba，其支持RabbitMQ、 Kafka，但提倡使用RocketMQ。</p></blockquote><h3 id="mq常见协议" tabindex="-1">MQ常见协议 <a class="header-anchor" href="#mq常见协议" aria-label="Permalink to &quot;MQ常见协议&quot;">​</a></h3><p>一般情况下MQ的实现是要遵循一些常规性协议的。常见的协议如下：</p><h4 id="jms" tabindex="-1">JMS <a class="header-anchor" href="#jms" aria-label="Permalink to &quot;JMS&quot;">​</a></h4><blockquote><p>JMS，Java Messaging Service（Java消息服务）。是Java平台上有关MOM（Message Oriented Middleware，面向消息的中间件 PO/OO/AO）的技术规范，它便于消息系统中的Java应用程序进行消 息交换，并且通过提供标准的产生、发送、接收消息的接口，简化企业应用的开发。ActiveMQ是该协 议的典型实现。</p></blockquote><h4 id="stomp" tabindex="-1">STOMP <a class="header-anchor" href="#stomp" aria-label="Permalink to &quot;STOMP&quot;">​</a></h4><blockquote><p>STOMP，Streaming Text Orientated Message Protocol（面向流文本的消息协议），是一种MOM设计 的简单文本协议。STOMP提供一个可互操作的连接格式，允许客户端与任意STOMP消息代理 （Broker）进行交互。ActiveMQ是该协议的典型实现，RabbitMQ通过插件可以支持该协议。</p></blockquote><h4 id="amqp" tabindex="-1">AMQP <a class="header-anchor" href="#amqp" aria-label="Permalink to &quot;AMQP&quot;">​</a></h4><blockquote><p>AMQP，Advanced Message Queuing Protocol（高级消息队列协议），一个提供统一消息服务的应用 层标准，是应用层协议的一个开放标准，是一种MOM设计。基于此协议的客户端与消息中间件可传递 消息，并不受客户端/中间件不同产品，不同开发语言等条件的限制。 RabbitMQ是该协议的典型实 现。</p></blockquote><h4 id="mqtt" tabindex="-1">MQTT <a class="header-anchor" href="#mqtt" aria-label="Permalink to &quot;MQTT&quot;">​</a></h4><blockquote><p>MQTT，Message Queuing Telemetry Transport（消息队列遥测传输），是IBM开发的一个即时通讯协 议，是一种二进制协议，主要用于服务器和低功耗IoT（物联网）设备间的通信。该协议支持所有平 台，几乎可以把所有联网物品和外部连接起来，被用来当做传感器和致动器的通信协议。 RabbitMQ通 过插件可以支持该协议。</p></blockquote><h2 id="rocketmq简介" tabindex="-1">RocketMQ简介 <a class="header-anchor" href="#rocketmq简介" aria-label="Permalink to &quot;RocketMQ简介&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011536427.png" alt="image-20230601153645326" style="zoom:80%;"><blockquote><p>RocketMQ是一个统一消息引擎、轻量级数据处理平台。RocketMQ是⼀款阿⾥巴巴开源的消息中间件。2016年11⽉28⽇，阿⾥巴巴向 Apache 软件基⾦会捐赠RocketMQ，成为 Apache 孵化项⽬。2017 年 9 ⽉ 25 ⽇，Apache 宣布 RocketMQ孵化成为 Apache 顶级项⽬（TLP ），成为国内⾸个互联⽹中间件在 Apache 上的顶级项⽬。官⽹地址：<a href="http://rocketmq.apache.org" target="_blank" rel="noreferrer">http://rocketmq.apache.org</a></p></blockquote><h3 id="rocketmq发展历程" tabindex="-1">RocketMQ发展历程 <a class="header-anchor" href="#rocketmq发展历程" aria-label="Permalink to &quot;RocketMQ发展历程&quot;">​</a></h3><blockquote><p>2007年，阿里开始五彩石项目，Notify作为项目中交易核心消息流转系统，应运而生。</p><p>2010年，B2B大规模使用ActiveMQ作为阿里的消息内核。阿里急需一个具有海量堆积能力的消息系统。</p></blockquote><blockquote><p>2011年初，Kafka开源。淘宝中间件团队在对Kafka进行了深入研究后，开发了一款新的MQ，MetaQ。</p><p>2012年，MetaQ发展到了v3.0版本，在它基础上进行了进一步的抽象，形成了RocketMQ</p><p>2015年，阿里在RocketMQ的基础上，又推出了一款专门针对阿里云上用户的消息系统Aliware MQ。</p><p>2016年双十一，RocketMQ承载了万亿级消息的流转，跨越了一个新的里程碑。11⽉28⽇，阿⾥巴巴 向 Apache 软件基⾦会捐赠 RocketMQ，成为 Apache 孵化项⽬。</p><p>2017 年 9 ⽉ 25 ⽇，Apache 宣布 RocketMQ孵化成为 Apache 顶级项⽬（TLP ），成为国内⾸个互联 ⽹中间件在 Apache 上的顶级项⽬。</p></blockquote><h2 id="基本概念" tabindex="-1">基本概念 <a class="header-anchor" href="#基本概念" aria-label="Permalink to &quot;基本概念&quot;">​</a></h2><h3 id="消息-message" tabindex="-1">消息（Message） <a class="header-anchor" href="#消息-message" aria-label="Permalink to &quot;消息（Message）&quot;">​</a></h3><blockquote><p><strong>消息系统所传输信息的物理载体，生产和消费数据的最小单位，每条消息必须属于一个主题。</strong></p></blockquote><h3 id="主题-topic" tabindex="-1">主题（Topic） <a class="header-anchor" href="#主题-topic" aria-label="Permalink to &quot;主题（Topic）&quot;">​</a></h3><blockquote><p><strong>Topic表示一类消息的集合，每个主题包含若干条消息，每条消息只能属于一个主题，是RocketMQ进行</strong><strong>消息订阅的基本单位</strong>。</p></blockquote><blockquote><p><strong>一个生产者可以同时发送多种Topic的消息；而一个消费者只对某种特定的Topic感兴趣，即只可以订阅</strong><strong>和消费一种Topic的消息</strong>。</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">topic:message</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">1</span><span style="color:#C3E88D;">:n</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#FFCB6B;">message:topic</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">1</span><span style="color:#C3E88D;">:1</span></span>
<span class="line"><span style="color:#FFCB6B;">producer:topic</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#C3E88D;">:n</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#FFCB6B;">consumer:topic</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#C3E88D;">:1</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306012134727.png" alt="image-20230601213438494" style="zoom:67%;"><h3 id="标签-tag" tabindex="-1">标签（Tag） <a class="header-anchor" href="#标签-tag" aria-label="Permalink to &quot;标签（Tag）&quot;">​</a></h3><blockquote><p>为消息设置的标签，用于同一主题下区分不同类型的消息。来自同一业务单元的消息，可以根据不同业 务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性，并优化RocketMQ提 供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑，实现更好的扩展性。</p></blockquote><blockquote><p><strong>Topic是消息的一级分类，Tag是消息的二级分类</strong>。示例如下</p></blockquote><blockquote><p>Topic：货物，tag=上海，tag=江苏，tag=浙江</p></blockquote><blockquote><p>------- 消费者 -----</p><p>topic=货物 tag = 上海</p><p>topic=货物 tag = 上海|浙江</p><p>topic=货物 tag = *</p></blockquote><h3 id="队列-queue" tabindex="-1">队列（Queue） <a class="header-anchor" href="#队列-queue" aria-label="Permalink to &quot;队列（Queue）&quot;">​</a></h3><blockquote><p><strong>存储消息的物理实体。一个Topic中可以包含多个Queue，每个Queue中存放的就是该Topic的消息。一</strong><strong>个Topic的Queue也被称为一个Topic中消息的分区（Partition）</strong>。</p></blockquote><blockquote><p><strong>一个Topic的Queue中的消息只能被一个消费者组中的一个消费者消费。一个Queue中的消息不允许同</strong><strong>一个消费者组中的多个消费者同时消费。</strong></p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306012145246.png" alt="image-20230601214532076" style="zoom:67%;"><blockquote><p>在学习参考其它相关资料时，还会看到一个概念：分片（Sharding）。</p></blockquote><blockquote><p><strong>分片不同于分区。在RocketMQ中，分片指的是存放相应Topic的Broker。每个分片中会创建出相应数量的分区，即Queue，每个Queue的大小都是相同的</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306012146831.png" alt="image-20230601214630701" style="zoom:80%;"><h3 id="消息标识-messageid-key" tabindex="-1">消息标识（MessageId/Key） <a class="header-anchor" href="#消息标识-messageid-key" aria-label="Permalink to &quot;消息标识（MessageId/Key）&quot;">​</a></h3><blockquote><p>RocketMQ中<strong>每个消息拥有唯一的MessageId，且可以携带具有业务标识的Key，以方便对消息的查询</strong>。</p></blockquote><blockquote><p>不过需要注意的是，<strong>MessageId有两个：在生产者send()消息时会自动生成一个MessageId（msgId)，</strong><strong>当消息到达Broker后，Broker也会自动生成一个MessageId(offsetMsgId)。msgId、offsetMsgId与key都</strong><strong>称为消息标识</strong>。</p></blockquote><blockquote><ul><li>msgId：由producer端生成，其生成规则为：</li><li>producerIp + 进程pid + MessageClientIDSetter类的ClassLoader的hashCode +当前时间 + AutomicInteger自增计数器</li><li>offsetMsgId：由broker端生成，其生成规则为： brokerIp + 物理分区的offset（Queue中的偏移量）</li><li>key：由用户指定的业务相关的唯一标识</li></ul></blockquote><h2 id="系统架构" tabindex="-1">系统架构 <a class="header-anchor" href="#系统架构" aria-label="Permalink to &quot;系统架构&quot;">​</a></h2><p>RocketMQ架构上主要分为四部分构成：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306012149973.png" alt="image-20230601214940790" style="zoom:80%;"><h3 id="producer" tabindex="-1">Producer <a class="header-anchor" href="#producer" aria-label="Permalink to &quot;Producer&quot;">​</a></h3><blockquote><p><strong>消息生产者，负责生产消息。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投</strong><strong>递，投递的过程支持快速失败并且低延迟</strong>。</p></blockquote><blockquote><ul><li>例如，<strong>业务系统产生的日志写入到MQ的过程，就是消息生产的过程</strong></li><li>再如，<strong>电商平台中用户提交的秒杀请求写入到MQ的过程，就是消息生产的过程</strong></li></ul></blockquote><blockquote><p>RocketMQ中的消息生产者都是以<strong>生产者组</strong>（Producer Group）的形式出现的。<strong>生产者组是同一类生产</strong><strong>者的集合，这类Producer发送相同Topic类型的消息</strong>。<strong>一个生产者组可以同时发送多个主题的消息</strong>。</p></blockquote><h3 id="consumer" tabindex="-1">Consumer <a class="header-anchor" href="#consumer" aria-label="Permalink to &quot;Consumer&quot;">​</a></h3><blockquote><p><strong>消息消费者，负责消费消息。消息消费者会从Broker服务器中获取到消息，并对消息进行相关业务处理</strong></p></blockquote><blockquote><p>例如，<strong>QoS系统从MQ中读取日志，并对日志进行解析处理的过程就是消息消费的过程</strong>。</p><p>再如，<strong>电商平台的业务系统从MQ中读取到秒杀请求，并对请求进行处理的过程就是消息消费的过程</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306012154906.png" alt="image-20230601215406717" style="zoom:80%;"><blockquote><p>RocketMQ中的消息消费者都是以<strong>消费者组</strong>（Consumer Group）的形式出现的。<strong>消费者组是同一类消费者的集合，这类Consumer消费的是同一个Topic类型的消息。消费者组使得在消息消费方面，实现负载均衡</strong>（将一个Topic中的不同的Queue平均分配给同一个Consumer Group的不同的Consumer)</p></blockquote><blockquote><p>注意，<strong>并不是将消息负载均衡，而是Queue负载均衡</strong>）和容错（<strong>一个Consmer挂了，该Consumer Group中的其它Consumer可以接着消费原Consumer消费的Queue</strong>）</p></blockquote><blockquote><p><strong>消费者组中Consumer的数量应该小于等于订阅Topic的Queue数量。如果超出Queue数量，则多出的Consumer将不能消费消息</strong>。</p></blockquote><blockquote><p>不过，一个Topic类型的消息可以被多个消费者组同时消费。注意，</p><p>1）<strong>消费者组只能消费一个Topic的消息，不能同时消费多个Topic消息</strong></p><p>2）<strong>一个消费者组中的消费者必须订阅完全相同的Topic</strong></p></blockquote><h3 id="name-server" tabindex="-1">Name Server <a class="header-anchor" href="#name-server" aria-label="Permalink to &quot;Name Server&quot;">​</a></h3><h4 id="功能介绍" tabindex="-1">功能介绍 <a class="header-anchor" href="#功能介绍" aria-label="Permalink to &quot;功能介绍&quot;">​</a></h4><blockquote><p><strong>NameServer是一个Broker与Topic路由的注册中心，支持Broker的动态注册与发现</strong>。RocketMQ的思想来自于Kafka，而Kafka是依赖了Zookeeper的。所以，在RocketMQ的早期版本，即在MetaQ v1.0与v2.0版本中，也是依赖于Zookeeper的。从MetaQ v3.0，即RocketMQ开始去掉了Zookeeper依赖，使用了自己的NameServer。</p></blockquote><p>主要包括两个功能：</p><blockquote><p><strong>Broker管理</strong>：接受Broker集群的注册信息并且保存下来作为路由信息的基本数据；提供心跳检测机制，检查Broker是否还存活。</p><p><strong>路由信息管理</strong>：每个NameServer中都保存着Broker集群的整个路由信息和用于客户端查询的队列信息。Producer和Conumser通过NameServer可以获取整个Broker集群的路由信息，进行消息的投递和消费</p></blockquote><h4 id="路由注册" tabindex="-1">路由注册 <a class="header-anchor" href="#路由注册" aria-label="Permalink to &quot;路由注册&quot;">​</a></h4><blockquote><p>NameServer通常也是以集群的方式部署，不过，NameServer是无状态的，即NameServer集群中的各 个节点间是无差异的，各节点间相互不进行信息通讯。</p></blockquote><blockquote><p>那各节点中的数据是如何进行数据同步的呢？在Broker节点启动时，轮询NameServer列表，与每个NameServer节点建立长连接，发起注册请求。在NameServer内部维护着⼀个Broker列表，用来动态存储Broker的信息。注意，这是与其它像zk、Eureka、Nacos等注册中心不同的地方。</p></blockquote><blockquote><p>这种NameServer的无状态方式，有什么优缺点：</p><p>优点：NameServer集群搭建简单，扩容简单。</p><p>缺点：对于Broker，必须明确指出所有NameServer地址。否则未指出的将不会去注册。</p></blockquote><blockquote><p>也正因为如此，<strong>NameServer并不能随便扩容。因为，若Broker不重新配置，新增的NameServer对于Broker来说是不可见的，其不会向这个NameServer进行注册</strong>。</p></blockquote><blockquote><p>Broker节点为了证明自己是活着的，为了维护与NameServer间的长连接，会将最新的信息以心跳包的 方式上报给NameServer，每30秒发送一次心跳。心跳包中包含 BrokerId、Broker地址(IP+Port)、 Broker名称、Broker所属集群名称等等。NameServer在接收到心跳包后，会更新心跳时间戳，记录这 个Broker的最新存活时间。</p></blockquote><h4 id="路由剔除" tabindex="-1">路由剔除 <a class="header-anchor" href="#路由剔除" aria-label="Permalink to &quot;路由剔除&quot;">​</a></h4><blockquote><p>由于Broker关机、宕机或网络抖动等原因，NameServer没有收到Broker的心跳，NameServer可能会将 其从Broker列表中剔除。</p></blockquote><blockquote><p>NameServer中有⼀个定时任务，每隔10秒就会扫描⼀次Broker表，查看每一个Broker的最新心跳时间 戳距离当前时间是否超过120秒，如果超过，则会判定Broker失效，然后将其从Broker列表中剔除。扩展：对于RocketMQ日常运维工作，例如Broker升级，需要停掉Broker的工作。OP需要怎么做？</p></blockquote><blockquote><p>OP需要将Broker的读写权限禁掉。一旦client(Consumer或Producer)向broker发送请求，都会收到broker的NO_PERMISSION响应，然后client会进行对其它Broker的重试。当OP观察到这个Broker没有流量后，再关闭它，实现Broker从NameServer的移除。</p></blockquote><blockquote><ul><li>OP：运维工程师</li><li>SRE：Site Reliability Engineer，现场可靠性工程师</li></ul></blockquote><h4 id="路由发现" tabindex="-1">路由发现 <a class="header-anchor" href="#路由发现" aria-label="Permalink to &quot;路由发现&quot;">​</a></h4><blockquote><p>RocketMQ的路由发现采用的是Pull模型。当Topic路由信息出现变化时，NameServer不会主动推送给 客户端，而是客户端定时拉取主题最新的路由。默认客户端每30秒会拉取一次最新的路由。</p></blockquote><blockquote><p>1）Push模型：推送模型。其实时性较好，是一个“发布-订阅”模型，需要维护一个长连接。而长连接的维护是需要资源成本。该模型适合于的场景：实时性要求较高、Client数量不多，Server数据变化频繁</p><p>2）Pull模型：拉取模型。存在的问题是，实时性较差。</p><p>3）Long Polling模型：长轮询模型。其是对Push与Pull模型的整合，充分利用了这两种模型的优 势，屏蔽了它们的劣势。</p></blockquote><h4 id="客户端nameserver选择策略" tabindex="-1">客户端NameServer选择策略 <a class="header-anchor" href="#客户端nameserver选择策略" aria-label="Permalink to &quot;客户端NameServer选择策略&quot;">​</a></h4><blockquote><p>这里的客户端指的是Producer与Consumer</p></blockquote><blockquote><p>客户端在配置时必须要写上NameServer集群的地址，那么客户端到底连接的是哪个NameServer节点 呢？<strong>客户端首先会生产一个随机数，然后再与NameServer节点数量取模，此时得到的就是所要连接的</strong><strong>节点索引，然后就会进行连接。如果连接失败，则会采用round-robin策略，逐个尝试着去连接其它节</strong><strong>点。首先采用的是随机策略进行的选择，失败后采用的是轮询策略</strong>。</p></blockquote><blockquote><p>扩展：Zookeeper Client是如何选择Zookeeper Server的？</p></blockquote><blockquote><p>简单来说就是，经过两次Shuffle，然后选择第一台Zookeeper Server。详细说就是，将配置文件中的zk server地址进行第一次shuffle，然后随机选择一个。这个选择出的一般都是一个hostname。然后获取到该hostname对应的所有ip，再对这些ip进行第二次 shuffle，从shuffle过的结果中取第一个server地址进行连接。</p></blockquote><h3 id="broker" tabindex="-1">Broker <a class="header-anchor" href="#broker" aria-label="Permalink to &quot;Broker&quot;">​</a></h3><h4 id="功能介绍-1" tabindex="-1">功能介绍 <a class="header-anchor" href="#功能介绍-1" aria-label="Permalink to &quot;功能介绍&quot;">​</a></h4><blockquote><p><strong>Broker充当着消息中转角色，负责存储消息、转发消息。Broker在RocketMQ系统中负责接收并存储从</strong><strong>生产者发送来的消息，同时为消费者的拉取请求作准备。Broker同时也存储着消息相关的元数据，包括</strong><strong>消费者组消费进度偏移offset、主题、队列等。Kafka 0.8版本之后，offset是存放在Broker中的，之前版本是存放在Zookeeper中的</strong>。</p></blockquote><h4 id="模块构成" tabindex="-1">模块构成 <a class="header-anchor" href="#模块构成" aria-label="Permalink to &quot;模块构成&quot;">​</a></h4><p>下图为Broker Server的功能模块示意图。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011554174.png" alt="image-20230601155423074" style="zoom:80%;"><blockquote><ul><li>Remoting Module：<strong>整个Broker的实体，负责处理来自clients端的请求</strong>。而这个Broker实体则由以下模块构成。</li><li>Client Manager：<strong>客户端管理器</strong>。负责接收、解析客户端(Producer/Consumer)请求，管理客户端。例如，维护Consumer的Topic订阅信息</li><li>Store Service：<strong>存储服务</strong>。提供方便简单的API接口，<strong>处理消息存储到物理硬盘和消息查询功能</strong>。</li><li>HA Service：<strong>高可用服务</strong>，提供Master Broker 和 Slave Broker之间的<strong>数据同步功能</strong>。</li><li>Index Service：<strong>索引服务</strong>。根据特定的Message key，对投递到Broker的消息进行索引服务，同时也提供根据Message Key<strong>对消息进行快速查询的功能</strong>。</li></ul></blockquote><h4 id="集群部署" tabindex="-1">集群部署 <a class="header-anchor" href="#集群部署" aria-label="Permalink to &quot;集群部署&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011555612.png" alt="image-20230601155513517" style="zoom:80%;"><blockquote><p>为了增强Broker性能与吞吐量，Broker一般都是以集群形式出现的。各集群节点中可能存放着相同Topic的不同Queue。不过，这里有个问题，如果某Broker节点宕机，如何保证数据不丢失呢？其解决方案是，将每个Broker集群节点进行横向扩展，即将Broker节点再建为一个HA集群，解决单点问题。</p></blockquote><blockquote><p>Broker节点集群是一个主从集群，即集群中具有Master与Slave两种角色。Master负责处理读写操作请 求，Slave负责对Master中的数据进行备份。当Master挂掉了，Slave则会自动切换为Master去工作。所 以这个Broker集群是主备集群。</p></blockquote><blockquote><p>一个Master可以包含多个Slave，但一个Slave只能隶属于一个Master。Master与Slave 的对应关系是通过指定相同的BrokerName、不同的BrokerId 来确定的。BrokerId为0表示Master，非0表示Slave。每个Broker与NameServer集群中的所有节点建立长连接，定时注册Topic信息到所有NameServer。</p></blockquote><h4 id="工作流程" tabindex="-1">工作流程 <a class="header-anchor" href="#工作流程" aria-label="Permalink to &quot;工作流程&quot;">​</a></h4><blockquote><ul><li><p>启动NameServer，NameServer启动后开始监听端口，等待Broker、Producer、Consumer连接。</p></li><li><p>启动Broker时，Broker会与所有的NameServer建立并保持长连接，然后每30秒向NameServer定时</p><p>发送心跳包。</p></li><li><p><strong>发送消息前，可以先创建Topic，创建Topic时需要指定该Topic要存储在哪些Broker上，当然，在创建Topic时也会将Topic与Broker的关系写入到NameServer中</strong>。<strong>不过，这步是可选的，也可以在发送消息时自动创建Topic</strong>。</p></li><li><p><strong>Producer发送消息，启动时先跟NameServer集群中的其中一台建立长连接，并从NameServer中获</strong></p><p><strong>取路由信息，即当前发送的Topic消息的Queue与Broker的地址（IP+Port）的映射关系。然后根据算法策略从队选择一个Queue，与队列所在的Broker建立长连接从而向Broker发消息</strong>。当然，在获取到路由信息后，Producer会首先将路由信息缓存到本地，再每30秒从NameServer更新一次路由信息。</p></li><li><p>Consumer跟Producer类似，跟其中一台NameServer建立长连接，获取其所订阅Topic的路由信息，然后根据算法策略从路由信息中获取到其所要消费的Queue，然后直接跟Broker建立长连接，开始消费其中的消息。Consumer在获取到路由信息后，同样也会每30秒从NameServer更新一次路由信息。不过不同于Producer的是，Consumer还会向Broker发送心跳，以确保Broker的存活状态。</p></li></ul></blockquote><h4 id="topic创建模式" tabindex="-1">Topic创建模式 <a class="header-anchor" href="#topic创建模式" aria-label="Permalink to &quot;Topic创建模式&quot;">​</a></h4><p>手动创建Topic时，有两种模式：</p><blockquote><ul><li><strong>集群模式</strong>：该模式下创建的Topic在该集群中，<strong>所有Broker中的Queue数量是相同的</strong>。</li><li><strong>Broker模式</strong>：该模式下创建的Topic在该集群中，<strong>每个Broker中的Queue数量可以不同</strong>。</li><li><strong>自动创建Topic时，默认采用的是Broker模式，会为每个Broker默认创建4个Queue</strong>。</li></ul></blockquote><blockquote><p>读/写队列：从物理上来讲，读/写队列是同一个队列。所以，不存在读/写队列数据同步问题。读/写队列是逻辑上进行区分的概念。一般情况下，读/写队列数量是相同的。</p></blockquote><blockquote><p>例如，创建Topic时设置的写队列数量为8，读队列数量为4，此时系统会创建8个Queue，分别是0 1 2 3 4 5 6 7。Producer会将消息写入到这8个队列，但Consumer只会消费0 1 2 3这4个队列中的消息，4 5 6 7中的消息是不会被消费到的。</p></blockquote><blockquote><p>再如，创建Topic时设置的写队列数量为4，读队列数量为8，此时系统会创建8个Queue，分别是0 1 2 3 4 5 6 7。Producer会将消息写入到0 1 2 3 这4个队列，但Consumer只会消费0 1 2 3 4 5 6 7这8个队列中 的消息，但是4 5 6 7中是没有消息的。此时假设Consumer Group中包含两个Consuer，Consumer1消 费0 1 2 3，而Consumer2消费4 5 6 7。但实际情况是，Consumer2是没有消息可消费的。也就是说，当读/写队列数量设置不同时，总是有问题的。</p></blockquote><blockquote><p>那么，为什么要这样设计呢？其这样设计的目的是为了，方便Topic的Queue的缩容。</p></blockquote><blockquote><p>例如，原来创建的Topic中包含16个Queue，如何能够使其Queue缩容为8个，还不会丢失消息？可以动 态修改写队列数量为8，读队列数量不变。此时新的消息只能写入到前8个队列，而消费都消费的却是 16个队列中的数据。当发现后8个Queue中的消息消费完毕后，就可以再将读队列数量动态设置为8。整 个缩容过程，没有丢失任何消息。</p></blockquote><blockquote><p>perm用于设置对当前创建Topic的操作权限：2表示只写，4表示只读，6表示读写。</p></blockquote><h1 id="rocketmq安装与启动" tabindex="-1">RocketMQ安装与启动 <a class="header-anchor" href="#rocketmq安装与启动" aria-label="Permalink to &quot;RocketMQ安装与启动&quot;">​</a></h1><h2 id="单机安装与启动" tabindex="-1">单机安装与启动 <a class="header-anchor" href="#单机安装与启动" aria-label="Permalink to &quot;单机安装与启动&quot;">​</a></h2><h3 id="准备工作" tabindex="-1">准备工作 <a class="header-anchor" href="#准备工作" aria-label="Permalink to &quot;准备工作&quot;">​</a></h3><p>软硬件需求，系统要求是64位的，JDK要求是1.8及其以上版本的。下载RocketMQ安装包</p><blockquote><p>官网：<a href="https://rocketmq.apache.org/zh/docs/quickStart/01quickstart/" target="_blank" rel="noreferrer">https://rocketmq.apache.org/zh/docs/quickStart/01quickstart/</a></p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306020919001.png" alt="image-20230602091937888" style="zoom:80%;"><blockquote><p>将下载的安装包上传到Linux。</p></blockquote><p><a href="https://blog.csdn.net/tianyu_yunlong_1/article/details/123078359" target="_blank" rel="noreferrer">https://blog.csdn.net/tianyu_yunlong_1/article/details/123078359</a></p><h3 id="安装binary版-推荐" tabindex="-1">安装binary版（推荐） <a class="header-anchor" href="#安装binary版-推荐" aria-label="Permalink to &quot;安装binary版（推荐）&quot;">​</a></h3><p>官网下载地址（下载慢）</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">wget https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">dlcdn</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">org</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">all</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">bin</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">release</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">zip</span></span></code></pre></div><p>改为清华源下载</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">wget https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">mirrors</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">tuna</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">tsinghua</span><span style="color:#F78C6C;">.edu</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">cn</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">apache</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">all</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">bin</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">release</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">zip</span></span></code></pre></div><p>解压到 /usr/local/rocketmq 目录</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">unzip rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">all</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">bin</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">release</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">zip </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">d </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">usr</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">local</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span></span></code></pre></div><p>进入rocketmq目录</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">cd </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">usr</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">local</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span></span>
<span class="line"><span style="color:#A6ACCD;">mv rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">all</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">bin</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">release</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span></span>
<span class="line"><span style="color:#A6ACCD;">cd rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span></span></code></pre></div><h3 id="修改内存配置" tabindex="-1">修改内存配置 <a class="header-anchor" href="#修改内存配置" aria-label="Permalink to &quot;修改内存配置&quot;">​</a></h3><h4 id="修改namesrv内存" tabindex="-1">修改namesrv内存 <a class="header-anchor" href="#修改namesrv内存" aria-label="Permalink to &quot;修改namesrv内存&quot;">​</a></h4><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">vim bin</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">runserver</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sh</span></span></code></pre></div><blockquote><p>修改前</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151341995.png" alt="image-20220515134151855" style="zoom:80%;"><blockquote><p>修改后</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151343807.png" alt="image-20220515134332670" style="zoom:80%;"><h4 id="修改broker内存" tabindex="-1">修改broker内存 <a class="header-anchor" href="#修改broker内存" aria-label="Permalink to &quot;修改broker内存&quot;">​</a></h4><blockquote><p>修改前：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151344240.png" alt="image-20220515134438166" style="zoom:80%;"><blockquote><p>修改后：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151345359.png" alt="image-20220515134522277" style="zoom:80%;"><h3 id="启动服务" tabindex="-1">启动服务 <a class="header-anchor" href="#启动服务" aria-label="Permalink to &quot;启动服务&quot;">​</a></h3><p>进入rocketmq安装目录后，创建日志目录</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">cd </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">usr</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">local</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">4.9.5</span></span></code></pre></div><h4 id="启动namesrv服务" tabindex="-1">启动namesrv服务 <a class="header-anchor" href="#启动namesrv服务" aria-label="Permalink to &quot;启动namesrv服务&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 启动namesrv</span></span>
<span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqnamesrv</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 验证namesrv是否启动成功</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/namesrv.log</span></span>
<span class="line"><span style="color:#FFCB6B;">The</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">Name</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">Server</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">boot</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">success...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 停止启动命令</span></span>
<span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqshutdown</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">namesrv</span></span></code></pre></div><h4 id="启动broker存储服务" tabindex="-1">启动broker存储服务 <a class="header-anchor" href="#启动broker存储服务" aria-label="Permalink to &quot;启动broker存储服务&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 后台启动</span></span>
<span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqbroker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-n</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">localhost:9876</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 查看启动日志</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/broker.log</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 停止服务</span></span>
<span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqshutdown</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">broker</span></span></code></pre></div><h4 id="查看启动状态" tabindex="-1">查看启动状态 <a class="header-anchor" href="#查看启动状态" aria-label="Permalink to &quot;查看启动状态&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">jps</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306020947161.png" alt="image-20230602094737050" style="zoom:80%;"><h3 id="关闭服务" tabindex="-1">关闭服务 <a class="header-anchor" href="#关闭服务" aria-label="Permalink to &quot;关闭服务&quot;">​</a></h3><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqshutdown</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">broker</span></span>
<span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqshutdown</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">namesrv</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306020957842.png" alt="image-20230602095758704" style="zoom:80%;"><h3 id="服务端口说明" tabindex="-1">服务端口说明 <a class="header-anchor" href="#服务端口说明" aria-label="Permalink to &quot;服务端口说明&quot;">​</a></h3><p>namesrv: 9876 （默认） broker:</p><blockquote><ul><li>ListenPort ：10911 （默认）</li><li>broker的vip通道端口为：ListenPort - 2 = 10909</li><li>broker的HA 通道端口为： ListenPort + 1 = 10912</li></ul></blockquote><blockquote><p>各服务启动后，查看端口信息（rocketmq纯Java开发）</p></blockquote><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">netstat </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">ntpl</span><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;">grep java</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151403580.png" alt="image-20220515140305527" style="zoom:80%;"><h3 id="测试消息" tabindex="-1">测试消息 <a class="header-anchor" href="#测试消息" aria-label="Permalink to &quot;测试消息&quot;">​</a></h3><p>使用自带的Producer和Consumer</p><p>注：如果生产端和消费端开了两个窗口，记得都执行此命令</p><h4 id="生产者" tabindex="-1">生产者 <a class="header-anchor" href="#生产者" aria-label="Permalink to &quot;生产者&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 命令行里依次执行</span></span>
<span class="line"><span style="color:#C792EA;">export</span><span style="color:#A6ACCD;"> NAMESRV_ADDR</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">127.0</span><span style="color:#C3E88D;">.0.1:9876</span></span>
<span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/tools.sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">org.apache.rocketmq.example.quickstart.Producer</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205151404151.png" alt="image-20220515140445054" style="zoom:80%;"><h4 id="消费者" tabindex="-1">消费者 <a class="header-anchor" href="#消费者" aria-label="Permalink to &quot;消费者&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 另开一个窗口</span></span>
<span class="line"><span style="color:#C792EA;">export</span><span style="color:#A6ACCD;"> NAMESRV_ADDR</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">127.0</span><span style="color:#C3E88D;">.0.1:9876</span></span>
<span class="line"><span style="color:#FFCB6B;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/tools.sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">org.apache.rocketmq.example.quickstart.Consumer</span></span></code></pre></div><blockquote><p><strong>注：</strong> 如果Consumer消费时报： RemotingConnectException: connect to null failed，则查看消费命令窗口是否声明了环境变量 export NAMESRV_ADDR=127.0.0.1:9876</p></blockquote><h2 id="控制台安装与启动" tabindex="-1">控制台安装与启动 <a class="header-anchor" href="#控制台安装与启动" aria-label="Permalink to &quot;控制台安装与启动&quot;">​</a></h2><p>官网：<a href="https://github.com/apache/rocketmq-dashboard" target="_blank" rel="noreferrer">https://github.com/apache/rocketmq-dashboard</a></p><h3 id="docker版" tabindex="-1">Docker版 <a class="header-anchor" href="#docker版" aria-label="Permalink to &quot;Docker版&quot;">​</a></h3><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">docker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">run</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-d</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">--name </span><span style="color:#C3E88D;">rocketmq-dashboard</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-e </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.88.101:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-p </span><span style="color:#F78C6C;">8080</span><span style="color:#C3E88D;">:8080</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-t </span><span style="color:#C3E88D;">apacherocketmq/rocketmq-dashboard:latest</span></span></code></pre></div><h3 id="jar包版" tabindex="-1">Jar包版 <a class="header-anchor" href="#jar包版" aria-label="Permalink to &quot;Jar包版&quot;">​</a></h3><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">git clone https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">hub</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">fastgit</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">xyz</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">apache</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">dashboard</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">git</span></span></code></pre></div><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">cd  rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">dashboard</span></span></code></pre></div><p>修改application.yml</p><div class="language-yml"><button title="Copy Code" class="copy"></button><span class="lang">yml</span><pre class="shiki material-theme-palenight has-diff"><code><span class="line"><span style="color:#F07178;">rocketmq</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F07178;">config</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;"># configure multiple namesrv addresses to manage multiple different clusters</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">namesrvAddrs</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">192.168.88.101:9876</span></span></code></pre></div><p>运行</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 下载依赖</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">跳过测试</span><span style="color:#89DDFF;">),</span><span style="color:#A6ACCD;">生成jar包</span></span>
<span class="line"><span style="color:#A6ACCD;">mvn clean package </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">Dmaven</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">test</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">skip</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">true</span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 运行jar包</span></span>
<span class="line"><span style="color:#A6ACCD;">java </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">jar target</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">dashboard</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1.0.1</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">SNAPSHOT</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">jar</span></span></code></pre></div><h3 id="访问测试" tabindex="-1">访问测试 <a class="header-anchor" href="#访问测试" aria-label="Permalink to &quot;访问测试&quot;">​</a></h3><p>访问：<a href="http://node1:8080" target="_blank" rel="noreferrer">http://node1:8080</a></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205231134355.png" alt="image-20220523113417260" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205231134807.png" alt="image-20220523113440755" style="zoom:80%;"><h2 id="集群搭建理论" tabindex="-1">集群搭建理论 <a class="header-anchor" href="#集群搭建理论" aria-label="Permalink to &quot;集群搭建理论&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011600067.png" alt="image-20230601160019960" style="zoom:80%;"><h3 id="数据复制与刷盘策略" tabindex="-1">数据复制与刷盘策略 <a class="header-anchor" href="#数据复制与刷盘策略" aria-label="Permalink to &quot;数据复制与刷盘策略&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011601233.png" alt="image-20230601160126145" style="zoom:80%;"><h3 id="复制策略" tabindex="-1">复制策略 <a class="header-anchor" href="#复制策略" aria-label="Permalink to &quot;复制策略&quot;">​</a></h3><blockquote><p>复制策略是Broker的Master与Slave间的数据同步方式。分为同步复制与异步复制：</p></blockquote><blockquote><ul><li>同步复制：消息写入master后，master会等待slave同步数据成功后才向producer返回成功ACK</li><li>异步复制：消息写入master后，master立即向producer返回成功ACK，无需等待slave同步数据成功</li><li>异步复制策略会降低系统的写入延迟，RT变小，提高了系统的吞吐量</li></ul></blockquote><h3 id="刷盘策略" tabindex="-1">刷盘策略 <a class="header-anchor" href="#刷盘策略" aria-label="Permalink to &quot;刷盘策略&quot;">​</a></h3><blockquote><p>刷盘策略指的是broker中消息的落盘方式，即消息发送到broker内存后消息持久化到磁盘的方式。分为 同步刷盘与异步刷盘：</p></blockquote><blockquote><ul><li>同步刷盘：当消息持久化到broker的磁盘后才算是消息写入成功。</li><li>异步刷盘：当消息写入到broker的内存后即表示消息写入成功，无需等待消息持久化到磁盘。</li></ul></blockquote><blockquote><ul><li>异步刷盘策略会降低系统的写入延迟，RT变小，提高了系统的吞吐量</li><li>消息写入到Broker的内存，一般是写入到了PageCache</li><li>对于异步 刷盘策略，消息会写入到PageCache后立即返回成功ACK。但并不会立即做落盘操作，而是当PageCache到达一定量时会自动进行落盘。</li></ul></blockquote><h3 id="broker集群模式" tabindex="-1">Broker集群模式 <a class="header-anchor" href="#broker集群模式" aria-label="Permalink to &quot;Broker集群模式&quot;">​</a></h3><p>根据Broker集群中各个节点间关系的不同，Broker集群可以分为以下几类：</p><h4 id="单master" tabindex="-1">单Master <a class="header-anchor" href="#单master" aria-label="Permalink to &quot;单Master&quot;">​</a></h4><blockquote><p>只有一个broker（其本质上就不能称为集群）。这种方式也只能是在测试时使用，生产环境下不能使 用，因为存在单点问题。</p></blockquote><h4 id="多master" tabindex="-1">多Master <a class="header-anchor" href="#多master" aria-label="Permalink to &quot;多Master&quot;">​</a></h4><blockquote><p>broker集群仅由多个master构成，不存在Slave。同一Topic各个Queue会平均分布在各个master节点上</p></blockquote><blockquote><p>优点：配置简单，单个Master宕机或重启维护对应用无影响，在磁盘配置为RAID10时，即使机器宕机不可恢复情况下，由于RAID10磁盘非常可靠，消息也不会丢（异步刷盘丢失少量消息，同步刷盘一条不丢），性能最高；</p></blockquote><blockquote><p>缺点：单台机器宕机期间，这台机器上未被消费的消息在机器恢复之前不可订阅（不可消费），消息实时性会受到影响。</p></blockquote><blockquote><p>以上优点的前提是，这些Master都配置了RAID磁盘阵列。如果没有配置，一旦出现某Master宕机，则会发生大量消息丢失的情况。</p></blockquote><h4 id="多master多slave模式-异步复制" tabindex="-1">多Master多Slave模式-异步复制 <a class="header-anchor" href="#多master多slave模式-异步复制" aria-label="Permalink to &quot;多Master多Slave模式-异步复制&quot;">​</a></h4><blockquote><p>broker集群由多个master构成，每个master又配置了多个slave（在配置了RAID磁盘阵列的情况下，一 个master一般配置一个slave即可）。master与slave的关系是主备关系，即master负责处理消息的读写 请求，而slave仅负责消息的备份与master宕机后的角色切换。</p></blockquote><blockquote><p>异步复制即前面所讲的复制策略中的异步复制策略，即消息写入master成功后，master立即向producer返回成功ACK，无需等待slave同步数据成功。该模式的最大特点之一是，当master宕机后slave能够自动切换为master。不过由于slave从master的同步具有短暂的延迟（毫秒级），所以当master宕机后，这种异步复制方式可能会存在少量消息的丢失问题。</p></blockquote><blockquote><p>Slave从Master同步的延迟越短，其可能丢失的消息就越少。对于Master的RAID磁盘阵列，若使用的也是异步复制策略，同样也存在延迟问题，同样也可能会丢失消息。但RAID阵列的秘诀是微秒级的（因为是由硬盘支持的），所以其丢失的数据量会更少。</p></blockquote><h4 id="多master多slave模式-同步双写" tabindex="-1">多Master多Slave模式-同步双写 <a class="header-anchor" href="#多master多slave模式-同步双写" aria-label="Permalink to &quot;多Master多Slave模式-同步双写&quot;">​</a></h4><blockquote><p>该模式是多Master多Slave模式的同步复制实现。所谓同步双写，指的是消息写入master成功后， master会等待slave同步数据成功后才向producer返回成功ACK，即master与slave都要写入成功后才会 返回成功ACK，也即双写。</p></blockquote><blockquote><p>该模式与异步复制模式相比，优点是消息的安全性更高，不存在消息丢失的情况。但单个消息的RT略 高，从而导致性能要略低（大约低10%）。该模式存在一个大的问题：对于目前的版本，Master宕机后，Slave 不会自动切换到Master。</p></blockquote><h4 id="最佳实践" tabindex="-1">最佳实践 <a class="header-anchor" href="#最佳实践" aria-label="Permalink to &quot;最佳实践&quot;">​</a></h4><blockquote><p>一般会为Master配置RAID10磁盘阵列，然后再为其配置一个Slave。即利用了RAID10磁盘阵列的高效、安全性，又解决了可能会影响订阅的问题。</p></blockquote><h2 id="磁盘阵列raid-补充" tabindex="-1">磁盘阵列RAID（补充） <a class="header-anchor" href="#磁盘阵列raid-补充" aria-label="Permalink to &quot;磁盘阵列RAID（补充）&quot;">​</a></h2><h3 id="raid历史" tabindex="-1">RAID历史 <a class="header-anchor" href="#raid历史" aria-label="Permalink to &quot;RAID历史&quot;">​</a></h3><blockquote><p>1988 年美国加州大学伯克利分校的 D. A. Patterson 教授等首次在论文 “A Case of Redundant Array of Inexpensive Disks” 中提出了 RAID 概念 ，即廉价冗余磁盘阵列（ Redundant Array of Inexpensive Disks ）。由于当时大容量磁盘比较昂贵， RAID 的基本思想是将多个容量较小、相对廉价的磁盘进行有机组合，从而以较低的成本获得与昂贵大容量磁盘相当的容量、性能、可靠性。随着磁盘成本和价格的不断降低， “廉价” 已经毫无意义。</p></blockquote><blockquote><p>因此， RAID 咨询委员会（ RAID Advisory Board, RAB ）决定用“ 独立 ” 替代 “ 廉价 ” ，于时 RAID 变成了独立磁盘冗余阵列（ Redundant Array of Independent Disks ）。但这仅仅是名称的变化，实质内容没有改变。内存：32m 6.4G（IBM 10.1G）</p></blockquote><h3 id="raid等级" tabindex="-1">RAID等级 <a class="header-anchor" href="#raid等级" aria-label="Permalink to &quot;RAID等级&quot;">​</a></h3><blockquote><p>RAID 这种设计思想很快被业界接纳， RAID 技术作为高性能、高可靠的存储技术，得到了非常广泛的 应用。 RAID 主要利用镜像、数据条带和数据校验三种技术来获取高性能、可靠性、容错能力和扩展 性，根据对这三种技术的使用策略和组合架构，可以把 RAID 分为不同的等级，以满足不同数据应用的 需求。</p></blockquote><blockquote><p>D. A. Patterson 等的论文中定义了 RAID0 ~ RAID6 原始 RAID 等级。随后存储厂商又不断推出 RAID7、 RAID10、RAID01 、 RAID50 、 RAID53 、 RAID100 等 RAID 等级，但这些并无统一的标准。目前 业界与学术界公认的标准是 RAID0 ~ RAID6 ，而在实际应用领域中使用最多的 RAID 等级是 RAID0 、 RAID1 、 RAID3 、 RAID5 、 RAID6 和 RAID10。RAID 每一个等级代表一种实现方法和技术，等级之间并无高低之分。在实际应用中，应当根据用户的数据应用特点，综合考虑可用性、性能和成本来选择合适的 RAID 等级，以及具体的实现方式。</p></blockquote><h3 id="关键技术" tabindex="-1">关键技术 <a class="header-anchor" href="#关键技术" aria-label="Permalink to &quot;关键技术&quot;">​</a></h3><h4 id="镜像技术" tabindex="-1">镜像技术 <a class="header-anchor" href="#镜像技术" aria-label="Permalink to &quot;镜像技术&quot;">​</a></h4><blockquote><p>镜像技术是一种冗余技术，为磁盘提供数据备份功能，防止磁盘发生故障而造成数据丢失。对于 RAID 而言，采用镜像技术最典型地的用法就是，同时在磁盘阵列中产生两个完全相同的数据副本，并且分布 在两个不同的磁盘上。镜像提供了完全的数据冗余能力，当一个数据副本失效不可用时，外部系统仍可 正常访问另一副本，不会对应用系统运行和性能产生影响。而且，镜像不需要额外的计算和校验，故障 修复非常快，直接复制即可。镜像技术可以从多个副本进行并发读取数据，提供更高的读 I/O 性能，但 不能并行写数据，写多个副本通常会导致一定的 I/O 性能下降。</p></blockquote><blockquote><p>镜像技术提供了非常高的数据安全性，其代价也是非常昂贵的，需要至少双倍的存储空间。高成本限制 了镜像的广泛应用，主要应用于至关重要的数据保护，这种场合的数据丢失可能会造成非常巨大的损失</p></blockquote><h4 id="数据条带技术" tabindex="-1">数据条带技术 <a class="header-anchor" href="#数据条带技术" aria-label="Permalink to &quot;数据条带技术&quot;">​</a></h4><blockquote><p>数据条带化技术是一种自动将 I/O操作负载均衡到多个物理磁盘上的技术。更具体地说就是，将一块连 续的数据分成很多小部分并把它们分别存储到不同磁盘上。这就能使多个进程可以并发访问数据的多个 不同部分，从而获得最大程度上的 I/O 并行能力，极大地提升性能。</p></blockquote><h4 id="数据校验技术" tabindex="-1">数据校验技术 <a class="header-anchor" href="#数据校验技术" aria-label="Permalink to &quot;数据校验技术&quot;">​</a></h4><blockquote><p>数据校验技术是指， RAID 要在写入数据的同时进行校验计算，并将得到的校验数据存储在 RAID 成员 磁盘中。校验数据可以集中保存在某个磁盘或分散存储在多个不同磁盘中。当其中一部分数据出错时， 就可以对剩余数据和校验数据进行反校验计算重建丢失的数据。</p></blockquote><blockquote><p>数据校验技术相对于镜像技术的优势在于节省大量开销，但由于每次数据读写都要进行大量的校验运 算，对计算机的运算速度要求很高，且必须使用硬件 RAID 控制器。在数据重建恢复方面，检验技术比 镜像技术复杂得多且慢得多。</p></blockquote><h3 id="raid分类" tabindex="-1">RAID分类 <a class="header-anchor" href="#raid分类" aria-label="Permalink to &quot;RAID分类&quot;">​</a></h3><p>从实现角度看， RAID 主要分为软 RAID、硬 RAID 以及混合 RAID 三种。</p><h4 id="软-raid" tabindex="-1">软 RAID <a class="header-anchor" href="#软-raid" aria-label="Permalink to &quot;软 RAID&quot;">​</a></h4><blockquote><p>所有功能均有操作系统和 CPU 来完成，没有独立的 RAID 控制处理芯片和 I/O 处理芯片，效率自然低。</p></blockquote><h4 id="硬-raid" tabindex="-1">硬 RAID <a class="header-anchor" href="#硬-raid" aria-label="Permalink to &quot;硬 RAID&quot;">​</a></h4><blockquote><p>配备了专门的 RAID 控制处理芯片和 I/O 处理芯片以及阵列缓冲，不占用 CPU 资源。效率很高，但成本也很高。</p></blockquote><h4 id="混合-raid" tabindex="-1">混合 RAID <a class="header-anchor" href="#混合-raid" aria-label="Permalink to &quot;混合 RAID&quot;">​</a></h4><blockquote><p>具备 RAID 控制处理芯片，但没有专门的I/O 处理芯片，需要 CPU 和驱动程序来完成。性能和成本在软 RAID 和硬 RAID 之间。</p></blockquote><h3 id="常见raid等级详解" tabindex="-1">常见RAID等级详解 <a class="header-anchor" href="#常见raid等级详解" aria-label="Permalink to &quot;常见RAID等级详解&quot;">​</a></h3><h4 id="jbod" tabindex="-1">JBOD <a class="header-anchor" href="#jbod" aria-label="Permalink to &quot;JBOD&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011608657.png" alt="image-20230601160856502" style="zoom:80%;"><blockquote><p>JBOD ，Just a Bunch of Disks，磁盘簇。表示一个没有控制软件提供协调控制的磁盘集合，这是 RAID 区别与 JBOD 的主要因素。 JBOD 将多个物理磁盘串联起来，提供一个巨大的逻辑磁盘。</p></blockquote><blockquote><p>JBOD 的数据存放机制是由第一块磁盘开始按顺序往后存储，当前磁盘存储空间用完后，再依次往后面 的磁盘存储数据。 JBOD 存储性能完全等同于单块磁盘，而且也不提供数据安全保护。</p></blockquote><blockquote><p>其只是简单提供一种扩展存储空间的机制，JBOD可用存储容量等于所有成员磁盘的存储空间之和JBOD 常指磁盘柜，而不论其是否提供 RAID 功能。不过，JBOD并非官方术语，官方称为Spanning。</p></blockquote><h4 id="raid0" tabindex="-1">RAID0 <a class="header-anchor" href="#raid0" aria-label="Permalink to &quot;RAID0&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011609954.png" alt="image-20230601160945876" style="zoom:80%;"><blockquote><p>RAID0 是一种简单的、无数据校验的数据条带化技术。实际上不是一种真正的 RAID ，因为它并不提 供任何形式的冗余策略。 RAID0 将所在磁盘条带化后组成大容量的存储空间，将数据分散存储在所有 磁盘中，以独立访问方式实现多块磁盘的并读访问。</p></blockquote><blockquote><p>理论上讲，一个由 n 块磁盘组成的 RAID0 ，它的读写性能是单个磁盘性能的 n 倍，但由于总线带宽等 多种因素的限制，实际的性能提升低于理论值。由于可以并发执行 I/O 操作，总线带宽得到充分利用。 再加上不需要进行数据校验， RAID0 的性能在所有 RAID 等级中是最高的。</p></blockquote><blockquote><p>RAID0 具有低成本、高读写性能、 100% 的高存储空间利用率等优点，但是它不提供数据冗余保护，一 旦数据损坏，将无法恢复。应用场景：对数据的顺序读写要求不高，对数据的安全性和可靠性要求不高，但对系统性能要求很高的场景。</p></blockquote><p>RAID0与JBOD相同点：</p><blockquote><p>1）存储容量：都是成员磁盘容量总和 2）磁盘利用率，都是100%，即都没有做任何的数据冗余备份</p></blockquote><p>RAID0与JBOD不同点：</p><blockquote><p>JBOD：数据是顺序存放的，一个磁盘存满后才会开始存放到下一个磁盘 RAID：各个磁盘中的数据写入是并行的，是通过数据条带技术写入的。其读写性能是JBOD的n倍</p></blockquote><h4 id="raid1" tabindex="-1">RAID1 <a class="header-anchor" href="#raid1" aria-label="Permalink to &quot;RAID1&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011611985.png" alt="image-20230601161103907" style="zoom:80%;"><blockquote><p>RAID1 就是一种镜像技术，它将数据完全一致地分别写到工作磁盘和镜像磁盘，它的磁盘空间利用率 为 50% 。 RAID1 在数据写入时，响应时间会有所影响，但是读数据的时候没有影响。 RAID1 提供了 最佳的数据保护，一旦工作磁盘发生故障，系统将自动切换到镜像磁盘，不会影响使用。</p></blockquote><blockquote><p>RAID1是为了增强数据安全性使两块磁盘数据呈现完全镜像，从而达到安全性好、技术简单、管理方 便。 RAID1 拥有完全容错的能力，但实现成本高。</p><p>应用场景：对顺序读写性能要求较高，或对数据安全性要求较高的场景。</p></blockquote><h3 id="raid10" tabindex="-1">RAID10 <a class="header-anchor" href="#raid10" aria-label="Permalink to &quot;RAID10&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011611475.png" alt="image-20230601161142364" style="zoom:80%;"><blockquote><p>RAID10是一个RAID1与RAID0的组合体，所以它继承了RAID0的快速和RAID1的安全。简单来说就是，先做条带，再做镜像。发即将进来的数据先分散到不同的磁盘，再将磁盘中的数据做镜像。</p></blockquote><h3 id="raid01" tabindex="-1">RAID01 <a class="header-anchor" href="#raid01" aria-label="Permalink to &quot;RAID01&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011612132.png" alt="image-20230601161212019" style="zoom:80%;"><blockquote><p>RAID01是一个RAID0与RAID1的组合体，所以它继承了RAID0的快速和RAID1的安全。简单来说就是，先做镜像再做条带。即将进来的数据先做镜像，再将镜像数据写入到与之前数据不同的磁盘，即再做条带。 RAID10要比RAID01的容错率再高，所以生产环境下一般是不使用RAID01的。</p></blockquote><h2 id="集群搭建与启动" tabindex="-1">集群搭建与启动 <a class="header-anchor" href="#集群搭建与启动" aria-label="Permalink to &quot;集群搭建与启动&quot;">​</a></h2><h3 id="集群架构" tabindex="-1">集群架构 <a class="header-anchor" href="#集群架构" aria-label="Permalink to &quot;集群架构&quot;">​</a></h3><blockquote><p>这里要搭建一个双主双从异步复制的Broker集群。为了方便，这里使用了两台主机来完成集群的搭建。这两台主机的功能与broker角色分配如下表。</p><p>注意：Master1 + Slave2和Master2 + Slave1组合可以避免一个挂了全都挂了的情况</p></blockquote><table><thead><tr><th>序号</th><th>主机名/IP</th><th>IP</th><th>功能</th><th>BROKER角色</th></tr></thead><tbody><tr><td>1</td><td>node1</td><td>192.168.88.101</td><td>NameServer + Broker</td><td>Master1 + Slave2</td></tr><tr><td>2</td><td>node2</td><td>192.168.88.102</td><td>NameServer + Broker</td><td>Master2 + Slave1</td></tr></tbody></table><h3 id="前置准备" tabindex="-1">前置准备 <a class="header-anchor" href="#前置准备" aria-label="Permalink to &quot;前置准备&quot;">​</a></h3><blockquote><p>node1搭建在上面，和ssh免密互通详见大数据集群搭建笔记，这里是将node1的RocketMQ复制到node2</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">ssh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">node2</span></span>
<span class="line"><span style="color:#FFCB6B;">mkdir</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-p</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">/usr/local/rocketmq/</span></span>
<span class="line"><span style="color:#FFCB6B;">scp</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-r</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">rocketmq-4.9.5</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">node2:</span><span style="color:#89DDFF;">`</span><span style="color:#82AAFF;">pwd</span><span style="color:#89DDFF;">`</span><span style="color:#FFCB6B;">/</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306021454033.png" alt="image-20230602145444908" style="zoom:80%;"><h3 id="node1" tabindex="-1">node1 <a class="header-anchor" href="#node1" aria-label="Permalink to &quot;node1&quot;">​</a></h3><h4 id="broker-a-properties" tabindex="-1">broker-a.properties <a class="header-anchor" href="#broker-a-properties" aria-label="Permalink to &quot;broker-a.properties&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#82AAFF;">cd</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">/usr/local/rocketmq/rocketmq-4.9.5/conf/2m-2s-async</span></span>
<span class="line"><span style="color:#FFCB6B;">vim</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">broker-a.properties</span></span></code></pre></div><p>将该配置文件内容修改为如下：</p><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 指定整个broker集群的名称，或者说是RocketMQ集群的名称</span></span>
<span class="line"><span style="color:#F07178;">brokerClusterName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">DefaultCluster</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定master-slave集群的名称。一个RocketMQ集群可以包含多个master-slave集群</span></span>
<span class="line"><span style="color:#F07178;">brokerName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">broker-a</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># master的brokerId为0</span></span>
<span class="line"><span style="color:#F07178;">brokerId</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">0</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定删除消息存储过期文件的时间为凌晨4点</span></span>
<span class="line"><span style="color:#F07178;">deleteWhen</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">04</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定未发生更新的消息存储文件的保留时长为48小时，48小时后过期，将会被删除</span></span>
<span class="line"><span style="color:#F07178;">fileReservedTime</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">48</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定当前broker为异步复制master</span></span>
<span class="line"><span style="color:#F07178;">brokerRole</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_MASTER</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定刷盘策略为异步刷盘</span></span>
<span class="line"><span style="color:#F07178;">flushDiskType</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_FLUSH</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定Name Server的地址</span></span>
<span class="line"><span style="color:#F07178;">namesrvAddr</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">192.168.88.101:9876</span><span style="color:#676E95;font-style:italic;">;192.168.88.102:9876</span></span></code></pre></div><h4 id="broker-b-s-properties" tabindex="-1">broker-b-s.properties <a class="header-anchor" href="#broker-b-s-properties" aria-label="Permalink to &quot;broker-b-s.properties&quot;">​</a></h4><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">vim</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">broker-b-s.properties</span></span></code></pre></div><p>将该配置文件内容修改为如下：</p><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">brokerClusterName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">DefaultCluster</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定这是另外一个master-slave集群</span></span>
<span class="line"><span style="color:#F07178;">brokerName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">broker-b</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># slave的brokerId为非0</span></span>
<span class="line"><span style="color:#F07178;">brokerId</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">1</span></span>
<span class="line"><span style="color:#F07178;">deleteWhen</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">04</span></span>
<span class="line"><span style="color:#F07178;">fileReservedTime</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">48</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定当前broker为slave</span></span>
<span class="line"><span style="color:#F07178;">brokerRole</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">SLAVE</span></span>
<span class="line"><span style="color:#F07178;">flushDiskType</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_FLUSH</span></span>
<span class="line"><span style="color:#F07178;">namesrvAddr</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">192.168.88.101:9876</span><span style="color:#676E95;font-style:italic;">;192.168.88.102:9876</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定Broker对外提供服务的端口，即Broker与producer与consumer通信的端口。默认10911。由于当前主机同时充当着master1与slave2，而前面的master1使用的是默认端口。这里需要将这两个端口加以区分，以区分出master1与slave2</span></span>
<span class="line"><span style="color:#F07178;">listenPort</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">11911</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定消息存储相关的路径。默认路径为~/store目录。由于当前主机同时充当着master1与slave2，master1使用的是默认路径，这里就需要再指定一个不同路径</span></span>
<span class="line"><span style="color:#F07178;">storePathRootDir</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s</span></span>
<span class="line"><span style="color:#F07178;">storePathCommitLog</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/commitlog</span></span>
<span class="line"><span style="color:#F07178;">storePathConsumeQueue</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/consumequeue</span></span>
<span class="line"><span style="color:#F07178;">storePathIndex</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/index</span></span>
<span class="line"><span style="color:#F07178;">storeCheckpoint</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/checkpoint</span></span>
<span class="line"><span style="color:#F07178;">abortFile</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/abort</span></span></code></pre></div><h3 id="node2" tabindex="-1">node2 <a class="header-anchor" href="#node2" aria-label="Permalink to &quot;node2&quot;">​</a></h3><blockquote><p>修改内容和上面一样，只是文件名变了</p></blockquote><h4 id="broker-b-properties" tabindex="-1">broker-b.properties <a class="header-anchor" href="#broker-b-properties" aria-label="Permalink to &quot;broker-b.properties&quot;">​</a></h4><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">brokerClusterName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">DefaultCluster</span></span>
<span class="line"><span style="color:#F07178;">brokerName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">broker-b</span></span>
<span class="line"><span style="color:#F07178;">brokerId</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">0</span></span>
<span class="line"><span style="color:#F07178;">deleteWhen</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">04</span></span>
<span class="line"><span style="color:#F07178;">fileReservedTime</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">48</span></span>
<span class="line"><span style="color:#F07178;">brokerRole</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_MASTER</span></span>
<span class="line"><span style="color:#F07178;">flushDiskType</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_FLUSH</span></span>
<span class="line"><span style="color:#F07178;">namesrvAddr</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">192.168.88.101:9876</span><span style="color:#676E95;font-style:italic;">;192.168.88.102:9876</span></span></code></pre></div><h4 id="broker-a-s-properties" tabindex="-1">broker-a-s.properties <a class="header-anchor" href="#broker-a-s-properties" aria-label="Permalink to &quot;broker-a-s.properties&quot;">​</a></h4><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">brokerClusterName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">DefaultCluster</span></span>
<span class="line"><span style="color:#F07178;">brokerName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">broker-a</span></span>
<span class="line"><span style="color:#F07178;">brokerId</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">1</span></span>
<span class="line"><span style="color:#F07178;">deleteWhen</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">04</span></span>
<span class="line"><span style="color:#F07178;">fileReservedTime</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">48</span></span>
<span class="line"><span style="color:#F07178;">brokerRole</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">SLAVE</span></span>
<span class="line"><span style="color:#F07178;">flushDiskType</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">ASYNC_FLUSH</span></span>
<span class="line"><span style="color:#F07178;">namesrvAddr</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">192.168.88.101:9876</span><span style="color:#676E95;font-style:italic;">;192.168.88.102:9876</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定Broker对外提供服务的端口，即Broker与producer与consumer通信的端口。默认10911。由于当前主机同时充当着master1与slave2，而前面的master1使用的是默认端口。这里需要将这两个端口加以区分，以区分出master1与slave2</span></span>
<span class="line"><span style="color:#F07178;">listenPort</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">11911</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 指定消息存储相关的路径。默认路径为~/store目录。由于当前主机同时充当着master1与slave2，master1使用的是默认路径，这里就需要再指定一个不同路径</span></span>
<span class="line"><span style="color:#F07178;">storePathRootDir</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s</span></span>
<span class="line"><span style="color:#F07178;">storePathCommitLog</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/commitlog</span></span>
<span class="line"><span style="color:#F07178;">storePathConsumeQueue</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/consumequeue</span></span>
<span class="line"><span style="color:#F07178;">storePathIndex</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/index</span></span>
<span class="line"><span style="color:#F07178;">storeCheckpoint</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/checkpoint</span></span>
<span class="line"><span style="color:#F07178;">abortFile</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">~/store-s/abort</span></span></code></pre></div><h3 id="其它配置-可选" tabindex="-1">其它配置(可选) <a class="header-anchor" href="#其它配置-可选" aria-label="Permalink to &quot;其它配置(可选)&quot;">​</a></h3><p>除了以上配置外，这些配置文件中还可以设置其它属性。</p><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">#指定整个broker集群的名称，或者说是RocketMQ集群的名称</span></span>
<span class="line"><span style="color:#F07178;">brokerClusterName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">rocket-MS</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定master-slave集群的名称。一个RocketMQ集群可以包含多个master-slave集群</span></span>
<span class="line"><span style="color:#F07178;">brokerName</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">broker-a</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#0 表示 Master，&gt;0 表示 Slave</span></span>
<span class="line"><span style="color:#F07178;">brokerId</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">0</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#nameServer地址，分号分割</span></span>
<span class="line"><span style="color:#F07178;">namesrvAddr</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">nameserver1:9876</span><span style="color:#676E95;font-style:italic;">;nameserver2:9876</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#默认为新建Topic所创建的队列数</span></span>
<span class="line"><span style="color:#F07178;">defaultTopicQueueNums</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">4</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#是否允许 Broker 自动创建Topic，建议生产环境中关闭</span></span>
<span class="line"><span style="color:#F07178;">autoCreateTopicEnable</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">true</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#是否允许 Broker 自动创建订阅组，建议生产环境中关闭</span></span>
<span class="line"><span style="color:#F07178;">autoCreateSubscriptionGroup</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">true</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#Broker对外提供服务的端口，即Broker与producer与consumer通信的端口</span></span>
<span class="line"><span style="color:#F07178;">listenPort</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">10911</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#HA高可用监听端口，即Master与Slave间通信的端口，默认值为listenPort+1</span></span>
<span class="line"><span style="color:#F07178;">haListenPort</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">10912</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定删除消息存储过期文件的时间为凌晨4点</span></span>
<span class="line"><span style="color:#F07178;">deleteWhen</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">04</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定未发生更新的消息存储文件的保留时长为48小时，48小时后过期，将会被删除</span></span>
<span class="line"><span style="color:#F07178;">fileReservedTime</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">48</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定commitLog目录中每个文件的大小，默认1G</span></span>
<span class="line"><span style="color:#F07178;">mapedFileSizeCommitLog</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">1073741824</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定ConsumeQueue的每个Topic的每个Queue文件中可以存放的消息数量，默认30w条</span></span>
<span class="line"><span style="color:#F07178;">mapedFileSizeConsumeQueue</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">300000</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#在清除过期文件时，如果该文件被其他线程所占用（引用数大于0，比如读取消息），此时会阻止此次删除任务，同时在第一次试图删除该文件时记录当前时间戳。该属性则表示从第一次拒绝删除后开始计时，该文件最多可以保留的时长。在此时间内若引用数仍不为0，则删除仍会被拒绝。不过时间到后，文件将被强制删除</span></span>
<span class="line"><span style="color:#F07178;">destroyMapedFileIntervalForcibly</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">120000</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定commitlog、consumequeue所在磁盘分区的最大使用率，超过该值，则需立即清除过期文件</span></span>
<span class="line"><span style="color:#F07178;">diskMaxUsedSpaceRatio</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">88</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定store目录的路径，默认在当前用户主目录中</span></span>
<span class="line"><span style="color:#F07178;">storePathRootDir</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#commitLog目录路径</span></span>
<span class="line"><span style="color:#F07178;">storePathCommitLog</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store/commitlog</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#consumeueue目录路径</span></span>
<span class="line"><span style="color:#F07178;">storePathConsumeQueue</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store/consumequeue</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#index目录路径</span></span>
<span class="line"><span style="color:#F07178;">storePathIndex</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store/index</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#checkpoint文件路径</span></span>
<span class="line"><span style="color:#F07178;">storeCheckpoint</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store/checkpoint</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#abort文件路径</span></span>
<span class="line"><span style="color:#F07178;">abortFile</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">/usr/local/rocketmq-all-4.5.0/store/abort</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#指定消息的最大大小</span></span>
<span class="line"><span style="color:#F07178;">maxMessageSize</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">65536</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#Broker的角色</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># - ASYNC_MASTER 异步复制Master</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># - SYNC_MASTER 同步双写Master</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># - SLAVE</span></span>
<span class="line"><span style="color:#F07178;">brokerRole</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">SYNC_MASTER</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#刷盘策略</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># - ASYNC_FLUSH 异步刷盘</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># - SYNC_FLUSH 同步刷盘</span></span>
<span class="line"><span style="color:#F07178;">flushDiskType</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">SYNC_FLUSH</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#发消息线程池数量</span></span>
<span class="line"><span style="color:#F07178;">sendMessageThreadPoolNums</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">128</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#拉消息线程池数量</span></span>
<span class="line"><span style="color:#F07178;">pullMessageThreadPoolNums</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">128</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#强制指定本机IP，需要根据每台机器进行修改。官方介绍可为空，系统默认自动识别，但多网卡时IP地址可能读取错误</span></span>
<span class="line"><span style="color:#F07178;">brokerIP1</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">192.168.3.105</span></span></code></pre></div><h3 id="启动集群" tabindex="-1">启动集群 <a class="header-anchor" href="#启动集群" aria-label="Permalink to &quot;启动集群&quot;">​</a></h3><h4 id="启动nameserver集群" tabindex="-1">启动NameServer集群 <a class="header-anchor" href="#启动nameserver集群" aria-label="Permalink to &quot;启动NameServer集群&quot;">​</a></h4><blockquote><p>分别启动node1与node2两个主机中的NameServer。启动命令完全相同</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqnamesrv</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/namesrv.log</span></span></code></pre></div><h4 id="启动master集群" tabindex="-1">启动Master集群 <a class="header-anchor" href="#启动master集群" aria-label="Permalink to &quot;启动Master集群&quot;">​</a></h4><p>分别启动node1与node2两个主机中的broker master。注意，它们指定所要加载的配置文件是不同的。</p><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqbroker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-c</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">conf/2m-2s-async/broker-a.properties</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/broker.log</span></span></code></pre></div><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqbroker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-c</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">conf/2m-2s-async/broker-b.properties</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/broker.log</span></span></code></pre></div><h4 id="启动slave集群" tabindex="-1">启动Slave集群 <a class="header-anchor" href="#启动slave集群" aria-label="Permalink to &quot;启动Slave集群&quot;">​</a></h4><blockquote><p>分别启动node1与node2两个主机中的broker slave。注意，它们指定所要加载的配置文件是不同的。</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqbroker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-c</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">conf/2m-2s-async/broker-b-s.properties</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/broker.log</span></span></code></pre></div><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">nohup</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">sh</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">bin/mqbroker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-c</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">conf/2m-2s-async/broker-a-s.properties</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&amp;</span></span>
<span class="line"><span style="color:#FFCB6B;">tail</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-f</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">~/logs/rocketmqlogs/broker.log</span></span></code></pre></div><h3 id="控制台配置" tabindex="-1">控制台配置 <a class="header-anchor" href="#控制台配置" aria-label="Permalink to &quot;控制台配置&quot;">​</a></h3><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">docker</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">run</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-d</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">--name </span><span style="color:#C3E88D;">rocketmq-dashboard</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-e </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-p </span><span style="color:#F78C6C;">8080</span><span style="color:#C3E88D;">:8080</span><span style="color:#A6ACCD;"> \</span></span>
<span class="line"><span style="color:#A6ACCD;">-t </span><span style="color:#C3E88D;">apacherocketmq/rocketmq-dashboard:latest</span></span></code></pre></div><p><a href="http://192.168.88.101:8080/#/cluster" target="_blank" rel="noreferrer">http://192.168.88.101:8080/#/cluster</a></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306021517976.png" alt="image-20230602151718816" style="zoom:80%;"><h2 id="mqadmin命令" tabindex="-1">mqadmin命令 <a class="header-anchor" href="#mqadmin命令" aria-label="Permalink to &quot;mqadmin命令&quot;">​</a></h2><blockquote><p>在mq解压目录的bin目录下有一个mqadmin命令，该命令是一个运维指令，用于对mq的主题，集群，broker 等信息进行管理。它的功能在控制台都能完成，因此作用不大</p></blockquote><h3 id="修改bin-tools-sh" tabindex="-1">修改bin/tools.sh <a class="header-anchor" href="#修改bin-tools-sh" aria-label="Permalink to &quot;修改bin/tools.sh&quot;">​</a></h3><blockquote><p>在运行mqadmin命令之前，先要修改mq解压目录下bin/tools.sh配置的JDK的ext目录位置。本机的ext 目录在/usr/java/jdk1.8.0_161/jre/lib/ext 。使用vim命令打开tools.sh文件，并在JAVA_OPT配置的-Djava.ext.dirs这一行的后面添加ext的路径。</p></blockquote><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">JAVA_OPT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F07178;">JAVA_OPT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${JAVA_OPT} -Djava.ext.dirs=${BASE_DIR}/lib:${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext:/usr/java/jdk1.8.0_161/jre/lib/ext</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F07178;">JAVA_OPT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${JAVA_OPT} -cp ${CLASSPATH}</span><span style="color:#89DDFF;">&quot;</span></span></code></pre></div><h3 id="运行mqadmin" tabindex="-1">运行mqadmin <a class="header-anchor" href="#运行mqadmin" aria-label="Permalink to &quot;运行mqadmin&quot;">​</a></h3><blockquote><p>直接运行该命令，可以看到其可以添加的commands。通过这些commands可以完成很多的功能。</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#FFCB6B;">./bin/mqadmin</span></span></code></pre></div><h3 id="该命令的官网详解" tabindex="-1">该命令的官网详解 <a class="header-anchor" href="#该命令的官网详解" aria-label="Permalink to &quot;该命令的官网详解&quot;">​</a></h3><blockquote><p>该命令在官网中有详细的用法解释。</p><p><a href="https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md" target="_blank" rel="noreferrer">https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md</a></p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011625950.png" alt="image-20230601162520856" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011625663.png" alt="image-20230601162540550" style="zoom:80%;"><h1 id="rocketmq工作原理" tabindex="-1">RocketMQ工作原理 <a class="header-anchor" href="#rocketmq工作原理" aria-label="Permalink to &quot;RocketMQ工作原理&quot;">​</a></h1><h2 id="消息的生产" tabindex="-1">消息的生产 <a class="header-anchor" href="#消息的生产" aria-label="Permalink to &quot;消息的生产&quot;">​</a></h2><h3 id="消息的生产过程" tabindex="-1">消息的生产过程 <a class="header-anchor" href="#消息的生产过程" aria-label="Permalink to &quot;消息的生产过程&quot;">​</a></h3><p>Producer可以将消息写入到某Broker中的某Queue中，其经历了如下过程：</p><blockquote><ul><li>Producer发送消息之前，会先向NameServer发出获取消息Topic的路由信息的请求</li><li>NameServer返回该Topic的路由表及Broker列表</li><li>Producer根据代码中指定的Queue选择策略，从Queue列表中选出一个队列，用于后续存储消息</li><li>Produer对消息做一些特殊处理，例如，消息本身超过4M，则会对其进行压缩</li><li>Producer向选择出的Queue所在的Broker发出RPC请求，将消息发送到选择出的Queue</li></ul></blockquote><blockquote><p>路由表：实际是一个Map，key为Topic名称，value是一个QueueData实例列表。QueueData并不是一个Queue对应一个QueueData，而是一个Broker中该Topic的所有Queue对应一个QueueData。即，只要涉及到该Topic的Broker，一个Broker对应一个QueueData。QueueData中包含brokerName。简单来说，路由表的key为Topic名称，value则为所有涉及该Topic的BrokerName列表。</p></blockquote><blockquote><p>Broker列表：其实际也是一个Map。key为brokerName，value为BrokerData。一个Broker对应一个BrokerData实例，对吗？不对。一套brokerName名称相同的Master-Slave小集群对应一个BrokerData。BrokerData中包含brokerName及一个map。该map的key为brokerId，value为该broker对应的地址。brokerId为0表示该broker为Master，非0表示Slave。</p></blockquote><h3 id="queue选择算法" tabindex="-1">Queue选择算法 <a class="header-anchor" href="#queue选择算法" aria-label="Permalink to &quot;Queue选择算法&quot;">​</a></h3><p>对于无序消息，其Queue选择算法，也称为消息投递算法，常见的有两种：</p><h4 id="轮询算法" tabindex="-1">轮询算法 <a class="header-anchor" href="#轮询算法" aria-label="Permalink to &quot;轮询算法&quot;">​</a></h4><blockquote><p><strong>默认选择算法。该算法保证了每个Queue中可以均匀的获取到消息。该算法存在一个问题：由于某些原因，在某些Broker上的Queue可能投递延迟较严重。从而导致Producer的缓存队列中出现较大的消息积压，影响消息的投递性能</strong>。</p></blockquote><h4 id="最小投递延迟算法" tabindex="-1">最小投递延迟算法 <a class="header-anchor" href="#最小投递延迟算法" aria-label="Permalink to &quot;最小投递延迟算法&quot;">​</a></h4><blockquote><p><strong>该算法会统计每次消息投递的时间延迟，然后根据统计出的结果将消息投递到时间延迟最小的Queue。</strong><strong>如果延迟相同，则采用轮询算法投递。该算法可以有效提升消息的投递性能</strong>。</p></blockquote><blockquote><p>该算法也存在一个问题：消息在Queue上的分配不均匀。<strong>投递延迟小的Queue其可能会存在大量的消息。而对该Queue的消费者压力会增大，降低消息的消费能力，可能会导致MQ中消息的堆积</strong>。</p></blockquote><h2 id="消息的存储" tabindex="-1">消息的存储 <a class="header-anchor" href="#消息的存储" aria-label="Permalink to &quot;消息的存储&quot;">​</a></h2><h3 id="文件总览" tabindex="-1">文件总览 <a class="header-anchor" href="#文件总览" aria-label="Permalink to &quot;文件总览&quot;">​</a></h3><p>RocketMQ中的消息存储在本地文件系统中，这些相关文件默认在当前用户主目录下的store目录中。</p><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#82AAFF;">cd</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">/root/store</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306021533643.png" alt="image-20230602153340526" style="zoom:80%;"><blockquote><ul><li>abort：该文件在Broker启动后会自动创建，正常关闭Broker，该文件会自动消失。若在没有启动Broker的情况下，发现这个文件是存在的，则说明之前Broker的关闭是非正常关闭。</li><li>checkpoint：其中存储着commitlog、consumequeue、index文件的最后刷盘时间戳</li><li>commitlog：其中存放着commitlog文件，而消息是写在commitlog文件中的</li><li>config：存放着Broker运行期间的一些配置数据</li><li>consumequeue：其中存放着consumequeue文件，队列就存放在这个目录中</li><li>index：其中存放着消息索引文件indexFile</li><li>lock：运行期间使用到的全局资源锁</li></ul></blockquote><h3 id="commitlog" tabindex="-1">commitlog <a class="header-anchor" href="#commitlog" aria-label="Permalink to &quot;commitlog&quot;">​</a></h3><blockquote><p>说明：在很多资料中commitlog目录中的文件简单就称为commitlog文件。但在源码中，该文件被命名为mappedFile。</p></blockquote><h4 id="目录与文件" tabindex="-1">目录与文件 <a class="header-anchor" href="#目录与文件" aria-label="Permalink to &quot;目录与文件&quot;">​</a></h4><blockquote><p>commitlog目录中存放着很多的mappedFile文件，当前Broker中的所有消息都是落盘到这些mappedFile文件中的。mappedFile文件大小为1G（小于等于1G），文件名由20位十进制数构成，表示当前文件的第一条消息的起始位移偏移量。</p></blockquote><blockquote><p>第一个文件名一定是20位0构成的。因为第一个文件的第一条消息的偏移量commitlog offset为0</p><p>当第一个文件放满时，则会自动生成第二个文件继续存放消息。</p><p>假设第一个文件大小是1073741820字节（1G = 1073741824字节），</p><p>则第二个文件名就是00000000001073741824。</p></blockquote><blockquote><p>以此类推，第n个文件名应该是前n-1个文件大小之和。一个Broker中所有mappedFile文件的commitlog offset是连续的</p></blockquote><blockquote><p>需要注意的是，一个Broker中仅包含一个commitlog目录，所有的mappedFile文件都是存放在该目录中 的。即无论当前Broker中存放着多少Topic的消息，这些消息都是被顺序写入到了mappedFile文件中 的。也就是说，这些消息在Broker中存放时并没有被按照Topic进行分类存放。</p></blockquote><blockquote><p>mappedFile文件是顺序读写的文件，所有其访问效率很高</p><p>无论是SSD磁盘还是SATA磁盘，通常情况下，顺序存取效率都会高于随机存取。</p></blockquote><h4 id="消息单元" tabindex="-1">消息单元 <a class="header-anchor" href="#消息单元" aria-label="Permalink to &quot;消息单元&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011629210.png" alt="image-20230601162956104" style="zoom:80%;"><blockquote><p>mappedFile文件内容由一个个的消息单元构成。每个消息单元中包含消息总长度MsgLen、消息的物理 位置physicalOffset、消息体内容Body、消息体长度BodyLength、消息主题Topic、Topic长度 TopicLength、消息生产者BornHost、消息发送时间戳BornTimestamp、消息所在的队列QueueId、消 息在Queue中存储的偏移量QueueOffset等近20余项消息相关属性。</p></blockquote><blockquote><p>需要注意到，消息单元中是包含Queue相关属性的。所以，我们在后续的学习中，就需要十分 留意commitlog与queue间的关系是什么？一个mappedFile文件中第m+1个消息单元的commitlog offset偏移量L(m+1) = L(m) + MsgLen(m) (m &gt;= 0)</p></blockquote><h3 id="consumequeue" tabindex="-1">consumequeue <a class="header-anchor" href="#consumequeue" aria-label="Permalink to &quot;consumequeue&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011630562.png" alt="image-20230601163044423" style="zoom:67%;"><h4 id="目录与文件-1" tabindex="-1">目录与文件 <a class="header-anchor" href="#目录与文件-1" aria-label="Permalink to &quot;目录与文件&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011631192.png" alt="image-20230601163121059" style="zoom:80%;"><blockquote><p>为了提高效率，会为每个Topic在~/store/consumequeue中创建一个目录，目录名为Topic名称。在该 Topic目录下，会再为每个该Topic的Queue建立一个目录，目录名为queueId。每个目录中存放着若干 consumequeue文件，consumequeue文件是commitlog的索引文件，可以根据consumequeue定位到具 体的消息。</p></blockquote><blockquote><p>consumequeue文件名也由20位数字构成，表示当前文件的第一个索引条目的起始位移偏移量。与 mappedFile文件名不同的是，其后续文件名是固定的。因为consumequeue文件大小是固定不变的。</p></blockquote><h4 id="索引条目" tabindex="-1">索引条目 <a class="header-anchor" href="#索引条目" aria-label="Permalink to &quot;索引条目&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011631532.png" alt="image-20230601163150433" style="zoom:80%;"><blockquote><p>每个consumequeue文件可以包含30w个索引条目，每个索引条目包含了三个消息重要属性：消息在 mappedFile文件中的偏移量CommitLog Offset、消息长度、消息Tag的hashcode值。这三个属性占20 个字节，所以每个文件的大小是固定的30w * 20字节。</p></blockquote><blockquote><p>一个consumequeue文件中所有消息的Topic一定是相同的。但每条消息的Tag可能是不同的。</p></blockquote><h3 id="对文件的读写" tabindex="-1">对文件的读写 <a class="header-anchor" href="#对文件的读写" aria-label="Permalink to &quot;对文件的读写&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011632953.png" alt="image-20230601163223844" style="zoom:80%;"><h4 id="消息写入" tabindex="-1">消息写入 <a class="header-anchor" href="#消息写入" aria-label="Permalink to &quot;消息写入&quot;">​</a></h4><blockquote><p>一条消息进入到Broker后经历了以下几个过程才最终被持久化。</p></blockquote><blockquote><p>Broker根据queueId，获取到该消息对应索引条目要在consumequeue目录中的写入偏移量，QueueOffset 将queueId、queueOffset等数据，与消息一起封装为消息单元将消息单元写入到commitlog。同时，形成消息索引条目将消息索引条目分发到相应的consumequeue</p></blockquote><h4 id="消息拉取" tabindex="-1">消息拉取 <a class="header-anchor" href="#消息拉取" aria-label="Permalink to &quot;消息拉取&quot;">​</a></h4><blockquote><p>当Consumer来拉取消息时会经历以下几个步骤：</p><ul><li>Consumer获取到其要消费消息所在Queue的消费偏移量offset ，计算出其要消费消息的消息offset</li><li>消费offset即消费进度，consumer对某个Queue的消费offset，即消费到了该Queue的第几条消息</li><li>消息offset = 消费offset + 1</li></ul></blockquote><blockquote><ul><li>Consumer向Broker发送拉取请求，其中会包含其要拉取消息的Queue、消息offset及消息Tag。</li><li>Broker计算在该consumequeue中的queueOffset。</li><li>queueOffset = 消息offset * 20字节</li></ul></blockquote><blockquote><p>从该queueOffset处开始向后查找第一个指定Tag的索引条目。解析该索引条目的前8个字节，即可定位到该消息在commitlog中的commitlog offset，从对应commitlog offset中读取消息单元，并发送给Consumer</p></blockquote><h4 id="性能提升" tabindex="-1">性能提升 <a class="header-anchor" href="#性能提升" aria-label="Permalink to &quot;性能提升&quot;">​</a></h4><blockquote><p>RocketMQ中，无论是消息本身还是消息索引，都是存储在磁盘上的。其不会影响消息的消费吗？当然 不会。其实RocketMQ的性能在目前的MQ产品中性能是非常高的。因为系统通过一系列相关机制大大 提升了性能。</p></blockquote><blockquote><p>首先，<strong>RocketMQ对文件的读写操作是通过mmap零拷贝进行的，将对文件的操作转化为直接对内存地</strong><strong>址进行操作，从而极大地提高了文件的读写效率</strong>。其次，consumequeue中的数据是顺序存放的，还引入了PageCache的预读取机制，使得对consumequeue文件的读取几乎接近于内存读取，即使在有消息堆积情况下也不会影响性能。</p></blockquote><blockquote><p><strong>PageCache机制，页缓存机制，是OS对文件的缓存机制，用于加速对文件的读写操作</strong>。一般来说，程序对文件进行顺序读写的速度几乎接近于内存读写速度，主要原因是由于OS使用PageCache机制对读写访问操作进行性能优化，将一部分的内存用作PageCache。</p></blockquote><blockquote><p><strong>写操作：OS会先将数据写入到PageCache中，随后会以异步方式由pd􀃦ush（page dirty flush)内核线程将Cache中的数据刷盘到物理磁盘</strong><strong>读操作：若用户要读取数据，其首先会从PageCache中读取，若没有命中，则OS在从物理磁盘上加载该数据到PageCache的同时，也会顺序对其相邻数据块中的数据进行预读取</strong>。</p></blockquote><blockquote><p>RocketMQ中可能会影响性能的是对commitlog文件的读取。因为对commitlog文件来说，读取消息时 会产生大量的随机访问，而随机访问会严重影响性能。不过，如果选择合适的系统IO调度算法，比如 设置调度算法为Deadline（采用SSD固态硬盘的话），随机读的性能也会有所提升。</p></blockquote><h3 id="与kafka的对比" tabindex="-1">与Kafka的对比 <a class="header-anchor" href="#与kafka的对比" aria-label="Permalink to &quot;与Kafka的对比&quot;">​</a></h3><blockquote><p>RocketMQ的很多思想来源于Kafka，其中commitlog与consumequeue就是。</p><p>RocketMQ中的commitlog目录与consumequeue的结合就类似于Kafka中的partition分区目录。</p><p>mappedFile文件就类似于Kafka中的segment段。</p><p>Kafka中的Topic的消息被分割为一个或多个partition。partition是一个物理概念，对应到系统上就是topic目录下的一个或多个目录。每个partition中包含的文件称为segment，是具体存放消息的文件。</p><p>Kafka中消息存放的目录结构是：topic目录下有partition目录，partition目录下有segment文件</p><p>Kafka中没有二级分类标签Tag这个概念</p></blockquote><blockquote><p>Kafka中无需索引文件。因为生产者是将消息直接写在了partition中的，消费者也是直接从partition中读取数据的</p></blockquote><h2 id="indexfile" tabindex="-1">indexFile <a class="header-anchor" href="#indexfile" aria-label="Permalink to &quot;indexFile&quot;">​</a></h2><blockquote><p><strong>除了通过通常的指定Topic进行消息消费外，RocketMQ还提供了根据key进行消息查询的功能。该查询是通过store目录中的index子目录中的indexFile进行索引实现的快速查询。当然，这个indexFile中的索引数据是在包含了key的消息被发送到Broker时写入的。如果消息中没有包含key，则不会写入</strong>。</p></blockquote><h3 id="索引条目结构" tabindex="-1">索引条目结构 <a class="header-anchor" href="#索引条目结构" aria-label="Permalink to &quot;索引条目结构&quot;">​</a></h3><blockquote><p><strong>每个Broker中会包含一组indexFile，每个indexFile都是以一个时间戳命名的（这个indexFile被创建时的时间戳）。每个indexFile文件由三部分构成：indexHeader，slots槽位，indexes索引数据。每个indexFile文件中包含500w个slot槽。而每个slot槽又可能会挂载很多的index索引单元</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011635632.png" alt="image-20230601163523546" style="zoom:80%;"><blockquote><p>indexHeader固定40个字节，其中存放着如下数据：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011635718.png" alt="image-20230601163543634" style="zoom:80%;"><blockquote><ul><li>beginTimestamp：该indexFile中第一条消息的存储时间</li><li>endTimestamp：该indexFile中最后一条消息存储时间</li><li>beginPhyoffset：该indexFile中第一条消息在commitlog中的偏移量commitlog offset</li><li>endPhyoffset：该indexFile中最后一条消息在commitlog中的偏移量commitlog offset</li><li>hashSlotCount：已经填充有index的slot数量（并不是每个slot槽下都挂载有index索引单元，这里统计的是所有挂载了index索引单元的slot槽的数量）</li><li>indexCount：该indexFile中包含的索引单元个数（统计出当前indexFile中所有slot槽下挂载的所有index索引单元的数量之和）</li></ul></blockquote><blockquote><p>indexFile中最复杂的是Slots与Indexes间的关系。在实际存储时，Indexes是在Slots后面的，但为了便 于理解，将它们的关系展示为如下形式：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011636935.png" alt="image-20230601163627823" style="zoom:80%;"><blockquote><p>key的hash值 % 500w 的结果即为slot槽位，然后将该slot值修改为该index索引单元的indexNo，根据这个indexNo可以计算出该index单元在indexFile中的位置。不过，该取模结果的重复率是很高的，</p></blockquote><blockquote><p>为了解决该问题，在每个index索引单元中增加了preIndexNo，用于指定该slot中当前index索引单元的 前一个index索引单元。而slot中始终存放的是其下最新的index索引单元的indexNo，这样的话，只要找到了slot就可以找到其最新的index索引单元，而通过这个index索引单元就可以找到其之前的所有index索引单元。</p></blockquote><blockquote><p>indexNo是一个在indexFile中的流水号，从0开始依次递增。即在一个indexFile中所有indexNo是以此递增的。indexNo在index索引单元中是没有体现的，其是通过indexes中依次数出来的。</p></blockquote><p>index索引单元默写20个字节，其中存放着以下四个属性：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011636107.png" alt="image-20230601163657015" style="zoom:80%;"><blockquote><ul><li>keyHash：消息中指定的业务key的hash值</li><li>phyOffset：当前key对应的消息在commitlog中的偏移量commitlog offset</li><li>timeDiff：当前key对应消息的存储时间与当前indexFile创建时间的时间差</li><li>preIndexNo：当前slot下当前index索引单元的前一个index索引单元的indexNo</li></ul></blockquote><h3 id="indexfile创建" tabindex="-1">indexFile创建 <a class="header-anchor" href="#indexfile创建" aria-label="Permalink to &quot;indexFile创建&quot;">​</a></h3><p>indexFile的文件名为当前文件被创建时的时间戳。这个时间戳有什么用处呢？</p><blockquote><p>根据业务key进行查询时，查询条件除了key之外，还需要指定一个要查询的时间戳，表示要查询不大于 该时间戳的最新的消息，即查询指定时间戳之前存储的最新消息。这个时间戳文件名可以简化查询，提 高查询效率。具体后面会详细讲解。</p></blockquote><p>indexFile文件是何时创建的？其创建的条件（时机）有两个：</p><blockquote><p>当第一条带key的消息发送来后，系统发现没有indexFile，此时会创建第一个indexFile文件当一个indexFile中挂载的index索引单元数量超出2000w个时，会创建新的indexFile。当带key的消息发送到来后，系统会找到最新的indexFile，并从其indexHeader的最后4字节中读取到indexCount。若indexCount &gt;= 2000w时，会创建新的indexFile。</p></blockquote><blockquote><p>由于可以推算出，一个indexFile的最大大小是：(40 + 500w * 4 + 2000w * 20)字节</p></blockquote><h3 id="查询流程" tabindex="-1">查询流程 <a class="header-anchor" href="#查询流程" aria-label="Permalink to &quot;查询流程&quot;">​</a></h3><blockquote><p>当消费者通过业务key来查询相应的消息时，其需要经过一个相对较复杂的查询流程。不过，在分析查询流程之前，首先要清楚几个定位计算式子：</p></blockquote><div class="language-sh"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 计算指定消息key的slot槽位序号：</span></span>
<span class="line"><span style="color:#FFCB6B;">slot槽位序号</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">key的hash</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">%</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">500</span><span style="color:#C3E88D;">w</span><span style="color:#A6ACCD;"> (式子1)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 计算槽位序号为n的slot在indexFile中的起始位置：</span></span>
<span class="line"><span style="color:#FFCB6B;">slot(n</span><span style="color:#A6ACCD;">)位置 = 40 + </span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">n</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> 4 </span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">式子2</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 计算indexNo为m的index在indexFile中的位置：</span></span>
<span class="line"><span style="color:#FFCB6B;">index(m</span><span style="color:#A6ACCD;">)位置 = 40 + 500w </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> 4 + </span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">m</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> 20 </span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">式子3</span><span style="color:#89DDFF;">)</span></span></code></pre></div><blockquote><p>40为indexFile中indexHeader的字节数、500w * 4 是所有slots所占的字节数</p></blockquote><p>具体查询流程如下：</p><p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011639418.png" alt="image-20230601163909282"></p><h2 id="消息的消费" tabindex="-1">消息的消费 <a class="header-anchor" href="#消息的消费" aria-label="Permalink to &quot;消息的消费&quot;">​</a></h2><blockquote><p>消费者从Broker中获取消息的方式有两种：pull拉取方式和push推动方式。消费者组对于消息消费的模式又分为两种：集群消费Clustering和广播消费Broadcasting。</p></blockquote><h3 id="获取消费类型" tabindex="-1">获取消费类型 <a class="header-anchor" href="#获取消费类型" aria-label="Permalink to &quot;获取消费类型&quot;">​</a></h3><h4 id="拉取式消费" tabindex="-1">拉取式消费 <a class="header-anchor" href="#拉取式消费" aria-label="Permalink to &quot;拉取式消费&quot;">​</a></h4><blockquote><p>Consumer主动从Broker中拉取消息，主动权由Consumer控制。一旦获取了批量消息，就会启动消费过 程。不过，该方式的实时性较弱，即Broker中有了新的消息时消费者并不能及时发现并消费。由于拉取时间间隔是由用户指定的，所以在设置该间隔时需要注意平稳：间隔太短，空请求比例会增加；间隔太长，消息的实时性太差</p></blockquote><h4 id="推送式消费" tabindex="-1">推送式消费 <a class="header-anchor" href="#推送式消费" aria-label="Permalink to &quot;推送式消费&quot;">​</a></h4><blockquote><p>该模式下Broker收到数据后会主动推送给Consumer。该获取方式一般实时性较高。</p></blockquote><blockquote><p>该获取方式是典型的发布-订阅模式，即Consumer向其关联的Queue注册了监听器，一旦发现有新的消息到来就会触发回调的执行，回调方法是Consumer去Queue中拉取消息。而这些都是基于Consumer与Broker间的长连接的。长连接的维护是需要消耗系统资源的。</p></blockquote><h4 id="对比" tabindex="-1">对比 <a class="header-anchor" href="#对比" aria-label="Permalink to &quot;对比&quot;">​</a></h4><blockquote><ul><li>pull：需要应用去实现对关联Queue的遍历，实时性差；但便于应用控制消息的拉取</li><li>push：封装了对关联Queue的遍历，实时性强，但会占用较多的系统资源</li></ul></blockquote><h3 id="消费模式" tabindex="-1">消费模式 <a class="header-anchor" href="#消费模式" aria-label="Permalink to &quot;消费模式&quot;">​</a></h3><h4 id="广播消费" tabindex="-1">广播消费 <a class="header-anchor" href="#广播消费" aria-label="Permalink to &quot;广播消费&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011640841.png" alt="image-20230601164044719" style="zoom:80%;"><blockquote><p>广播消费模式下，相同Consumer Group的每个Consumer实例都接收同一个Topic的全量消息。即每条消息都会被发送到Consumer Group中的每个Consumer。</p></blockquote><h4 id="集群消费" tabindex="-1">集群消费 <a class="header-anchor" href="#集群消费" aria-label="Permalink to &quot;集群消费&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011641586.png" alt="image-20230601164110455" style="zoom:80%;"><blockquote><p>集群消费模式下，相同Consumer Group的每个Consumer实例平均分摊同一个Topic的消息。即每条消 息只会被发送到Consumer Group中的某个Consumer。</p></blockquote><h4 id="消息进度保存" tabindex="-1">消息进度保存 <a class="header-anchor" href="#消息进度保存" aria-label="Permalink to &quot;消息进度保存&quot;">​</a></h4><blockquote><p>广播模式：消费进度保存在consumer端。因为广播模式下consumer group中每个consumer都会消费所有消息，但它们的消费进度是不同。所以consumer各自保存各自的消费进度。</p><p>集群模式：消费进度保存在broker中。consumer group中的所有consumer共同消费同一个Topic中的消息，同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中，故消费进度是需要共享的。下图是broker中存放的各个Topic的各个Queue的消费进度。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011642454.png" alt="image-20230601164200308" style="zoom:80%;"><h3 id="rebalance机制" tabindex="-1">Rebalance机制 <a class="header-anchor" href="#rebalance机制" aria-label="Permalink to &quot;Rebalance机制&quot;">​</a></h3><p>Rebalance机制讨论的前提是：集群消费。</p><h4 id="什么是rebalance" tabindex="-1">什么是Rebalance <a class="header-anchor" href="#什么是rebalance" aria-label="Permalink to &quot;什么是Rebalance&quot;">​</a></h4><blockquote><p>Rebalance即再均衡，指的是，将⼀个Topic下的多个Queue在同⼀个Consumer Group中的多个Consumer间进行重新分配的过程。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011642774.png" alt="image-20230601164237640" style="zoom:80%;"><blockquote><p>Rebalance机制的本意是为了提升消息的并行消费能力。例如，⼀个Topic下5个队列，在只有1个消费 者的情况下，这个消费者将负责消费这5个队列的消息。如果此时我们增加⼀个消费者，那么就可以给 其中⼀个消费者分配2个队列，给另⼀个分配3个队列，从而提升消息的并行消费能力。</p></blockquote><h4 id="rebalance限制" tabindex="-1">Rebalance限制 <a class="header-anchor" href="#rebalance限制" aria-label="Permalink to &quot;Rebalance限制&quot;">​</a></h4><blockquote><p>由于⼀个队列最多分配给⼀个消费者，因此当某个消费者组下的消费者实例数量大于队列的数量时， 多余的消费者实例将分配不到任何队列。</p></blockquote><h4 id="rebalance危害" tabindex="-1">Rebalance危害 <a class="header-anchor" href="#rebalance危害" aria-label="Permalink to &quot;Rebalance危害&quot;">​</a></h4><blockquote><p>Rebalance的在提升消费能力的同时，也带来一些问题：</p></blockquote><blockquote><p>消费暂停：在只有一个Consumer时，其负责消费所有队列；在新增了一个Consumer后会触发Rebalance的发生。此时原Consumer就需要暂停部分队列的消费，等到这些队列分配给新的Consumer后，这些暂停消费的队列才能继续被消费。</p></blockquote><blockquote><p>消费重复：Consumer 在消费新分配给自己的队列时，必须接着之前Consumer 提交的消费进度的offset 继续消费。然而默认情况下，offset是异步提交的，这个异步性导致提交到Broker的offset与Consumer 实际消费的消息并不一致。这个不一致的差值就是可能会重复消费的消息。</p></blockquote><blockquote><p>同步提交：consumer提交了其消费完毕的一批消息的offset给broker后，需要等待broker的成功ACK。当收到ACK后，consumer才会继续获取并消费下一批消息。在等待ACK期间，consumer是阻塞的。 异步提交：consumer提交了其消费完毕的一批消息的offset给broker后，不需要等待broker的成功ACK。consumer可以直接获取并消费下一批消息。</p><p>对于一次性读取消息的数量，需要根据具体业务场景选择一个相对均衡的是很有必要的。因为 数量过大，系统性能提升了，但产生重复消费的消息数量可能会增加；数量过小，系统性能会 下降，但被重复消费的消息数量可能会减少。</p></blockquote><blockquote><p>消费突刺：由于Rebalance可能导致重复消费，如果需要重复消费的消息过多，或者因为Rebalance暂停 时间过长从而导致积压了部分消息。那么有可能会导致在Rebalance结束之后瞬间需要消费很多消息。</p></blockquote><h4 id="rebalance产生的原因" tabindex="-1">Rebalance产生的原因 <a class="header-anchor" href="#rebalance产生的原因" aria-label="Permalink to &quot;Rebalance产生的原因&quot;">​</a></h4><blockquote><p>导致Rebalance产生的原因，无非就两个：消费者所订阅Topic的Queue数量发生变化，或消费者组中消 费者的数量发生变化。</p></blockquote><p>1）Queue数量发生变化的场景：</p><blockquote><ul><li>Broker扩容或缩容</li><li>Broker升级运维</li><li>Broker与NameServer间的网络异常</li><li>Queue扩容或缩容</li></ul></blockquote><p>2）消费者数量发生变化的场景：</p><blockquote><ul><li>Consumer Group扩容或缩容</li><li>Consumer升级运维</li><li>Consumer与NameServer间网络异常</li></ul></blockquote><h4 id="rebalance过程" tabindex="-1">Rebalance过程 <a class="header-anchor" href="#rebalance过程" aria-label="Permalink to &quot;Rebalance过程&quot;">​</a></h4><blockquote><p>在Broker中维护着多个Map集合，这些集合中动态存放着当前Topic中Queue的信息、Consumer Group 中Consumer实例的信息。一旦发现消费者所订阅的Queue数量发生变化，或消费者组中消费者的数量 发生变化，立即向Consumer Group中的每个实例发出Rebalance通知。</p></blockquote><blockquote><p>TopicConfigManager：key是topic名称，value是TopicConfig。TopicConfig中维护着该Topic中所 有Queue的数据。 ConsumerManager：key是Consumser Group Id，value是ConsumerGroupInfo。 ConsumerGroupInfo中维护着该Group中所有Consumer实例数据。 ConsumerOffsetManager：key为Topic与订阅该Topic的Group的组合,即topic@group， value是一个内层Map。内层Map的key为QueueId，内层Map的value为该Queue的消费进度offset。 Consumer实例在接收到通知后会采用Queue分配算法自己获取到相应的Queue，即由Consumer实例 自主进行Rebalance。</p></blockquote><h4 id="与kafka对比" tabindex="-1">与Kafka对比 <a class="header-anchor" href="#与kafka对比" aria-label="Permalink to &quot;与Kafka对比&quot;">​</a></h4><blockquote><p>在Kafka中，一旦发现出现了Rebalance条件，Broker会调用Group Coordinator来完成Rebalance。 Coordinator是Broker中的一个进程。Coordinator会在Consumer Group中选出一个Group Leader。由 这个Leader根据自己本身组情况完成Partition分区的再分配。这个再分配结果会上报给Coordinator， 并由Coordinator同步给Group中的所有Consumer实例。</p></blockquote><blockquote><p>Kafka中的Rebalance是由Consumer Leader完成的。而RocketMQ中的Rebalance是由每个Consumer自 身完成的，Group中不存在Leader。</p></blockquote><h3 id="queue分配算法" tabindex="-1">Queue分配算法 <a class="header-anchor" href="#queue分配算法" aria-label="Permalink to &quot;Queue分配算法&quot;">​</a></h3><blockquote><p>一个Topic中的Queue只能由Consumer Group中的一个Consumer进行消费，而一个Consumer可以同时 消费多个Queue中的消息。那么Queue与Consumer间的配对关系是如何确定的，即Queue要分配给哪 个Consumer进行消费，也是有算法策略的。常见的有四种策略。这些策略是通过在创建Consumer时的 构造器传进去的。</p></blockquote><h4 id="平均分配策略" tabindex="-1">平均分配策略 <a class="header-anchor" href="#平均分配策略" aria-label="Permalink to &quot;平均分配策略&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011645471.png" alt="image-20230601164510361" style="zoom:80%;"><blockquote><p>该算法是要根据avg = QueueCount / ConsumerCount 的计算结果进行分配的。如果能够整除，则按顺序将avg个Queue逐个分配Consumer；如果不能整除，则将多余出的Queue按照Consumer顺序逐个分配。 该算法即，先计算好每个Consumer应该分得几个Queue，然后再依次将这些数量的Queue逐个分配个Consumer。</p></blockquote><h4 id="环形平均策略" tabindex="-1">环形平均策略 <a class="header-anchor" href="#环形平均策略" aria-label="Permalink to &quot;环形平均策略&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011645032.png" alt="image-20230601164544915" style="zoom:80%;"><blockquote><p>环形平均算法是指，根据消费者的顺序，依次在由queue队列组成的环形图中逐个分配。 该算法不用事先计算每个Consumer需要分配几个Queue，直接一个一个分即可。</p></blockquote><h4 id="一致性hash策略" tabindex="-1">一致性hash策略 <a class="header-anchor" href="#一致性hash策略" aria-label="Permalink to &quot;一致性hash策略&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011646936.png" alt="image-20230601164617825" style="zoom:80%;"><blockquote><p>该算法会将consumer的hash值作为Node节点存放到hash环上，然后将queue的hash值也放到hash环 上，通过顺时针方向，距离queue最近的那个consumer就是该queue要分配的consumer。 该算法存在的问题：分配不均。</p></blockquote><h4 id="同机房策略" tabindex="-1">同机房策略 <a class="header-anchor" href="#同机房策略" aria-label="Permalink to &quot;同机房策略&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011646164.png" alt="image-20230601164644050" style="zoom:80%;"><blockquote><p>该算法会根据queue的部署机房位置和consumer的位置，过滤出当前consumer相同机房的queue。然 后按照平均分配策略或环形平均策略对同机房queue进行分配。如果没有同机房queue，则按照平均分 配策略或环形平均策略对所有queue进行分配。</p></blockquote><h4 id="对比-1" tabindex="-1">对比 <a class="header-anchor" href="#对比-1" aria-label="Permalink to &quot;对比&quot;">​</a></h4><p>一致性hash算法存在的问题：</p><blockquote><p>两种平均分配策略的分配效率较高，一致性hash策略的较低。因为一致性hash算法较复杂。另外，一 致性hash策略分配的结果也很大可能上存在不平均的情况。</p><p>一致性hash算法存在的意义：其可以有效减少由于消费者组扩容或缩容所带来的大量的Rebalance。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011647234.png" alt="image-20230601164736099" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011647107.png" alt="image-20230601164752961" style="zoom:80%;"><p>一致性hash算法的应用场景：Consumer数量变化较频繁的场景。</p><h3 id="至少一次原则" tabindex="-1">至少一次原则 <a class="header-anchor" href="#至少一次原则" aria-label="Permalink to &quot;至少一次原则&quot;">​</a></h3><blockquote><p>RocketMQ有一个原则：<strong>每条消息必须要被成功消费一次。那么什么是成功消费呢？Consumer在消费完消息后会向其消费进度记录器提交其消费消息的offset，offset被成功记录到记录器中，那么这条消费就被成功消费了</strong>。</p></blockquote><blockquote><p>什么是消费进度记录器？</p><ul><li>对于广播消费模式来说，Consumer本身就是消费进度记录器。</li><li>对于集群消费模式来说，Broker是消费进度记录器。</li></ul></blockquote><h2 id="订阅关系的一致性" tabindex="-1">订阅关系的一致性 <a class="header-anchor" href="#订阅关系的一致性" aria-label="Permalink to &quot;订阅关系的一致性&quot;">​</a></h2><blockquote><p>订阅关系的一致性指的是，<strong>同一个消费者组（Group ID相同）下所有Consumer实例所订阅的Topic与Tag及对消息的处理逻辑必须完全一致</strong>。否<strong>则，消息消费的逻辑就会混乱，甚至导致消息丢失</strong>。</p></blockquote><h3 id="正确订阅关系" tabindex="-1">正确订阅关系 <a class="header-anchor" href="#正确订阅关系" aria-label="Permalink to &quot;正确订阅关系&quot;">​</a></h3><blockquote><p>多个消费者组订阅了多个Topic，并且每个消费者组里的多个<strong>消费者实例的订阅关系保持了一致</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011701628.png" alt="image-20230601170141514" style="zoom:80%;"><h3 id="错误订阅关系" tabindex="-1">错误订阅关系 <a class="header-anchor" href="#错误订阅关系" aria-label="Permalink to &quot;错误订阅关系&quot;">​</a></h3><blockquote><p>一个消费者组订阅了多个Topic，但是该消费者组里的多个Consumer<strong>实例的订阅关系并没有保持一致</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011701705.png" alt="image-20230601170159593" style="zoom:80%;"><p>订阅了不同Topic</p><blockquote><ul><li>该例中的错误在于，同一个消费者组中的两个Consumer实例订阅了不同的Topic。</li><li>Consumer实例1-1：（订阅了topic为jodie_test_A，tag为所有的消息）</li><li>Consumer实例1-2：（订阅了topic为jodie_test_B，tag为所有的消息）</li></ul></blockquote><p>订阅了不同Tag</p><blockquote><ul><li>该例中的错误在于，同一个消费者组中的两个Consumer订阅了相同Topic的不同Tag。</li><li>Consumer实例2-1：（订阅了topic为jodie_test_A，tag为TagA的消息）</li><li>Consumer实例2-2：（订阅了topic为jodie_test_A，tag为所有的消息）</li></ul></blockquote><p>订阅了不同数量的Topic</p><blockquote><ul><li>该例中的错误在于，同一个消费者组中的两个Consumer订阅了不同数量的Topic。</li><li>Consumer实例3-1：（该Consumer订阅了两个Topic）</li><li>Consumer实例3-2：（该Consumer订阅了一个Topic）</li></ul></blockquote><h2 id="offset管理" tabindex="-1">offset管理 <a class="header-anchor" href="#offset管理" aria-label="Permalink to &quot;offset管理&quot;">​</a></h2><blockquote><p>这里的offset指的是Consumer的消费进度offset。消费进度offset是用来记录每个Queue的不同消费组的消费进度的。根据消费进度记录器的不同，可以分为两种模式：本地模式和远程模式。</p></blockquote><h3 id="offset本地管理模式" tabindex="-1">offset本地管理模式 <a class="header-anchor" href="#offset本地管理模式" aria-label="Permalink to &quot;offset本地管理模式&quot;">​</a></h3><blockquote><p>当消费模式为广播消费时，offset使用本地模式存储。因为每条消息会被所有的消费者消费，每个消费 者管理自己的消费进度，各个消费者之间不存在消费进度的交集。</p></blockquote><blockquote><p>Consumer在广播消费模式下offset相关数据以json的形式持久化到Consumer本地磁盘文件中，默认文 件路径为当前用户主目录下的.rocketmq_offsets/${clientId}/${group}/Offsets.json 。 其中${clientId}为当前消费者id，默认为ip@DEFAULT；${group}为消费者组名称。</p></blockquote><h3 id="offset远程管理模式" tabindex="-1">offset远程管理模式 <a class="header-anchor" href="#offset远程管理模式" aria-label="Permalink to &quot;offset远程管理模式&quot;">​</a></h3><blockquote><p>当消费模式为集群消费时，offset使用远程模式管理。因为所有Cosnumer实例对消息采用的是均衡消 费，所有Consumer共享Queue的消费进度。</p></blockquote><blockquote><p>Consumer在集群消费模式下offset相关数据以json的形式持久化到Broker磁盘文件中，文件路径为当前 用户主目录下的store/config/consumerOffset.json 。</p></blockquote><blockquote><p>Broker启动时会加载这个文件，并写入到一个双层Map（ConsumerOffsetManager）。外层map的key 为topic@group，value为内层map。内层map的key为queueId，value为offset。当发生Rebalance时， 新的Consumer会从该Map中获取到相应的数据来继续消费。集群模式下offset采用远程管理模式，主要是为了保证Rebalance机制。</p></blockquote><h3 id="offset用途" tabindex="-1">offset用途 <a class="header-anchor" href="#offset用途" aria-label="Permalink to &quot;offset用途&quot;">​</a></h3><blockquote><p>消费者是如何从最开始持续消费消息的？消费者要消费的第一条消息的起始位置是用户自己通过 consumer.setConsumeFromWhere()方法指定的。</p></blockquote><blockquote><p>在Consumer启动后，其要消费的第一条消息的起始位置常用的有三种，这三种位置可以通过枚举类型 常量设置。这个枚举类型为ConsumeFromWhere</p></blockquote><blockquote><p>CONSUME_FROM_LAST_OFFSET：从queue的当前最后一条消息开始消费 CONSUME_FROM_FIRST_OFFSET：从queue的第一条消息开始消费 CONSUME_FROM_TIMESTAMP：从指定的具体时间戳位置的消息开始消费。这个具体时间戳 是通过另外一个语句指定的 。</p></blockquote><blockquote><p>consumer.setConsumeTimestamp(“20210701080000”) yyyyMMddHHmmss</p></blockquote><blockquote><p>当消费完一批消息后，Consumer会提交其消费进度offset给Broker，Broker在收到消费进度后会将其更 新到那个双层Map（ConsumerOffsetManager）及consumerOffset.json文件中，然后向该Consumer进 行ACK，而ACK内容中包含三项数据：当前消费队列的最小offset（minOffset）、最大 offset（maxOffset）、及下次消费的起始offset（nextBeginOffset）。</p></blockquote><h3 id="重试队列" tabindex="-1">重试队列 <a class="header-anchor" href="#重试队列" aria-label="Permalink to &quot;重试队列&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011704710.png" alt="image-20230601170401583" style="zoom:80%;"><blockquote><p>当rocketMQ对消息的消费出现异常时，会将发生异常的消息的offset提交到Broker中的重试队列。系统 在发生消息消费异常时会为当前的topic@group创建一个重试队列，该队列以%RETRY%开头，到达重 试时间后进行消费重试。</p></blockquote><h3 id="offset的同步提交与异步提交" tabindex="-1">offset的同步提交与异步提交 <a class="header-anchor" href="#offset的同步提交与异步提交" aria-label="Permalink to &quot;offset的同步提交与异步提交&quot;">​</a></h3><blockquote><p>集群消费模式下，Consumer消费完消息后会向Broker提交消费进度offset，其提交方式分为两种： 同步提交： 消费者在消费完一批消息后会向broker提交这些消息的offset，然后等待broker的成功响 应。若在等待超时之前收到了成功响应，则继续读取下一批消息进行消费（从ACK中获取 nextBeginOffset）。若没有收到响应，则会重新提交，直到获取到响应。而在这个等待过程中，消费 者是阻塞的。其严重影响了消费者的吞吐量。</p></blockquote><blockquote><p>异步提交： 消费者在消费完一批消息后向broker提交offset，但无需等待Broker的成功响应，可以继续 读取并消费下一批消息。这种方式增加了消费者的吞吐量。但需要注意，broker在收到提交的offset 后，还是会向消费者进行响应的。可能还没有收到ACK，此时Consumer会从Broker中直接获取nextBeginOffset。</p></blockquote><h2 id="消费幂等" tabindex="-1">消费幂等 <a class="header-anchor" href="#消费幂等" aria-label="Permalink to &quot;消费幂等&quot;">​</a></h2><h3 id="什么是消费幂等" tabindex="-1">什么是消费幂等 <a class="header-anchor" href="#什么是消费幂等" aria-label="Permalink to &quot;什么是消费幂等&quot;">​</a></h3><blockquote><p><strong>当出现消费者对某条消息重复消费的情况时，重复消费的结果与消费一次的结果是相同的，并且多次消</strong><strong>费并未对业务系统产生任何负面影响，那么这个消费过程就是消费幂等的</strong>。</p></blockquote><blockquote><p><strong>幂等：若某操作执行多次与执行一次对系统产生的影响是相同的，则称该操作是幂等的。在互联网应用中，尤其在网络不稳定的情况下，消息很有可能会出现重复发送或重复消费。如果重复的消息可能会影响业务处理，那么就应该对消息做幂等处理</strong>。</p></blockquote><h3 id="消息重复的场景分析" tabindex="-1">消息重复的场景分析 <a class="header-anchor" href="#消息重复的场景分析" aria-label="Permalink to &quot;消息重复的场景分析&quot;">​</a></h3><blockquote><p>什么情况下可能会出现消息被重复消费呢？最常见的有以下三种情况：</p></blockquote><h4 id="发送时消息重复" tabindex="-1">发送时消息重复 <a class="header-anchor" href="#发送时消息重复" aria-label="Permalink to &quot;发送时消息重复&quot;">​</a></h4><blockquote><p>当一条消息已被成功发送到Broker并完成持久化，此时出现了网络闪断，从而导致Broker对Producer应 答失败。 如果此时Producer意识到消息发送失败并尝试再次发送消息，此时Broker中就可能会出现两 条内容相同并且Message ID也相同的消息，那么后续Consumer就一定会消费两次该消息。</p></blockquote><h4 id="消费时消息重复" tabindex="-1">消费时消息重复 <a class="header-anchor" href="#消费时消息重复" aria-label="Permalink to &quot;消费时消息重复&quot;">​</a></h4><blockquote><p>消息已投递到Consumer并完成业务处理，当Consumer给Broker反馈应答时网络闪断，Broker没有接收 到消费成功响应。<strong>为了保证消息至少被消费一次的原则</strong>，Broker将在网络恢复后再次尝试投递之前已 被处理过的消息。此时消费者就会收到与之前处理过的内容相同、Message ID也相同的消息。</p></blockquote><h4 id="rebalance时消息重复" tabindex="-1">Rebalance时消息重复 <a class="header-anchor" href="#rebalance时消息重复" aria-label="Permalink to &quot;Rebalance时消息重复&quot;">​</a></h4><blockquote><p>当Consumer Group中的Consumer数量发生变化时，或其订阅的Topic的Queue数量发生变化时，会触 发Rebalance，此时Consumer可能会收到曾经被消费过的消息。</p></blockquote><h3 id="通用解决方案" tabindex="-1">通用解决方案 <a class="header-anchor" href="#通用解决方案" aria-label="Permalink to &quot;通用解决方案&quot;">​</a></h3><h4 id="两要素" tabindex="-1">两要素 <a class="header-anchor" href="#两要素" aria-label="Permalink to &quot;两要素&quot;">​</a></h4><blockquote><p>幂等解决方案的设计中涉及到两项要素：<strong>幂等令牌，与唯一性处理</strong>。只要充分利用好这两要素，就可以 设计出好的幂等解决方案。</p></blockquote><blockquote><p><strong>幂等令牌：是生产者和消费者两者中的既定协议，通常指具备唯⼀业务标识的字符串。例如，订</strong><strong>单号、流水号。一般由Producer随着消息一同发送来的</strong>。</p></blockquote><blockquote><p><strong>唯一性处理：服务端通过采用⼀定的算法策略，保证同⼀个业务逻辑不会被重复执行成功多次。</strong><strong>例如，对同一笔订单的多次支付操作，只会成功一次</strong>。</p></blockquote><h4 id="解决方案" tabindex="-1">解决方案 <a class="header-anchor" href="#解决方案" aria-label="Permalink to &quot;解决方案&quot;">​</a></h4><p>对于常见的系统，幂等性操作的通用性解决方案是：</p><blockquote><p><strong>首先通过缓存去重。在缓存中如果已经存在了某幂等令牌，则说明本次操作是重复性操作；若缓</strong><strong>存没有命中，则进入下一步</strong>。</p></blockquote><blockquote><p><strong>在唯一性处理之前，先在数据库中查询幂等令牌作为索引的数据是否存在。若存在，则说明本次</strong><strong>操作为重复性操作；若不存在，则进入下一步</strong>。</p></blockquote><blockquote><p><strong>在同一事务中完成三项操作：唯一性处理后，将幂等令牌写入到缓存，并将幂等令牌作为唯一索</strong><strong>引的数据写入到DB中</strong>。</p></blockquote><blockquote><p>第1步已经判断过是否是重复性操作了，为什么第2步还要再次判断？能够进入第2步，说明已经 不是重复操作了，第2次判断是否重复？ 当然不重复。一般缓存中的数据是具有有效期的。缓存中数据的有效期一旦过期，就是发生缓 存穿透，使请求直接就到达了DBMS</p></blockquote><h3 id="解决方案-1" tabindex="-1">解决方案 <a class="header-anchor" href="#解决方案-1" aria-label="Permalink to &quot;解决方案&quot;">​</a></h3><p>以支付场景为例：</p><blockquote><p>当支付请求到达后，首先在Redis缓存中却获取key为<strong>支付流水号</strong>的缓存value。若value不空，则 说明本次支付是重复操作，业务系统直接返回调用侧重复支付标识；若value为空，则进入下一步</p></blockquote><blockquote><p>到DBMS中根据<strong>支付流水号</strong>查询是否存在相应实例。若存在，则说明本次支付是重复操作，业务系统直接返回调用侧重复支付标识；若不存在，则说明本次操作是首次操作，进入下一步完成唯一性处理</p></blockquote><blockquote><p>在分布式事务中完成三项操作：</p></blockquote><blockquote><ul><li>完成支付任务，将当前支付流水号作为key，任意字符串作为value，通过set(key, value, expireTime)将数据写入到Redis缓存</li><li>将当前支付流水号作为主键，与其它相关数据共同写入到DBMS</li></ul></blockquote><h3 id="消费幂等的实现" tabindex="-1">消费幂等的实现 <a class="header-anchor" href="#消费幂等的实现" aria-label="Permalink to &quot;消费幂等的实现&quot;">​</a></h3><blockquote><p>消费幂等的解决方案很简单：<strong>为消息指定不会重复的唯一标识。因为Message ID有可能出现重复的情</strong><strong>况，所以真正安全的幂等处理，不建议以Message ID作为处理依据。最好的方式是以业务唯一标识作为</strong><strong>幂等处理的关键依据，而业务的唯一标识可以通过消息Key设置</strong>。</p></blockquote><blockquote><p>以支付场景为例，<strong>可以将消息的Key设置为订单号，作为幂等处理的依据</strong>。具体代码示例如下：</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> message </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">message</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setKey</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">ORDERID_100</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">message</span><span style="color:#89DDFF;">);</span></span></code></pre></div><blockquote><p><strong>消费者收到消息时可以根据消息的Key即订单号来实现消费幂等</strong>：</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListenerConcurrently</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ConsumeConcurrentlyStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">consumeMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msgs</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                                    </span><span style="color:#C792EA;">ConsumeConcurrentlyContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">context</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> key </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getKeys</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 根据业务唯一标识Key做幂等处理</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// ……</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">});</span></span></code></pre></div><blockquote><p><strong>RocketMQ能够保证消息不丢失，但不能保证消息不重复</strong>。</p></blockquote><h2 id="消息堆积与消费延迟" tabindex="-1">消息堆积与消费延迟 <a class="header-anchor" href="#消息堆积与消费延迟" aria-label="Permalink to &quot;消息堆积与消费延迟&quot;">​</a></h2><h3 id="概念" tabindex="-1">概念 <a class="header-anchor" href="#概念" aria-label="Permalink to &quot;概念&quot;">​</a></h3><blockquote><p>消息处理流程中，<strong>如果Consumer的消费速度跟不上Producer的发送速度，MQ中未处理的消息会越来越多（进的多出的少），这部分消息就被称为堆积消息。消息出现堆积进而会造成消息的消费延迟</strong>。</p></blockquote><blockquote><p>以下场景需要重点关注消息堆积和消费延迟问题：</p><ul><li><strong>业务系统上下游能力不匹配造成的持续堆积，且无法自行恢复</strong>。</li><li><strong>业务系统对消息的消费实时性要求较高，即使是短暂的堆积造成的消费延迟也无法接受</strong>。</li></ul></blockquote><h3 id="产生原因" tabindex="-1">产生原因 <a class="header-anchor" href="#产生原因" aria-label="Permalink to &quot;产生原因&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011706021.png" alt="image-20230601170641862" style="zoom:80%;"><blockquote><p>Consumer使用长轮询Pull模式消费消息时，分为以下两个阶段：</p></blockquote><h4 id="消息拉取-1" tabindex="-1">消息拉取 <a class="header-anchor" href="#消息拉取-1" aria-label="Permalink to &quot;消息拉取&quot;">​</a></h4><blockquote><p>Consumer通过长轮询Pull模式批量拉取的方式从服务端获取消息，将拉取到的消息缓存到本地缓冲队列中。对于拉取式消费，在内网环境下会有很高的吞吐量，所以这一阶段一般不会成为消息堆积的瓶颈。 一个单线程单分区的低规格主机(Consumer，4C8G)，其可达到几万的TPS。如果是多个分区多个线程，则可以轻松达到几十万的TPS。</p></blockquote><h4 id="消息消费" tabindex="-1">消息消费 <a class="header-anchor" href="#消息消费" aria-label="Permalink to &quot;消息消费&quot;">​</a></h4><blockquote><p>Consumer将本地缓存的消息提交到消费线程中，使用业务消费逻辑对消息进行处理，处理完毕后获取 到一个结果。这是真正的消息消费过程。此时Consumer的消费能力就完全依赖于消息的消费耗时和消 费并发度了。如果由于业务处理逻辑复杂等原因，导致处理单条消息的耗时较长，则整体的消息吞吐 量肯定不会高，此时就会导致Consumer本地缓冲队列达到上限，停止从服务端拉取消息。</p></blockquote><h4 id="结论" tabindex="-1">结论 <a class="header-anchor" href="#结论" aria-label="Permalink to &quot;结论&quot;">​</a></h4><blockquote><p><strong>消息堆积的主要瓶颈在于客户端的消费能力，而消费能力由消费耗时和消费并发度决定。注意，消费</strong><strong>耗时的优先级要高于消费并发度。即在保证了消费耗时的合理性前提下，再考虑消费并发度问题</strong>。</p></blockquote><h3 id="消费耗时" tabindex="-1">消费耗时 <a class="header-anchor" href="#消费耗时" aria-label="Permalink to &quot;消费耗时&quot;">​</a></h3><blockquote><p><strong>影响消息处理时长的主要因素是代码逻辑</strong>。而代码逻辑中可能会影响处理时长代码主要有两种类型： <strong>CPU内部计算型代码和外部I/O操作型代码</strong>。</p></blockquote><blockquote><p><strong>通常情况下代码中如果没有复杂的递归和循环的话，内部计算耗时相对外部I/O操作来说几乎可以忽</strong><strong>略。所以外部IO型代码是影响消息处理时长的主要症结所在</strong>。</p></blockquote><p>外部IO操作型代码举例：</p><blockquote><ul><li><strong>读写外部数据库，例如对远程MySQL的访问</strong></li><li><strong>读写外部缓存系统，例如对远程Redis的访问</strong></li><li><strong>下游系统调用，例如Dubbo的RPC远程调用</strong></li></ul></blockquote><blockquote><p>Spring Cloud的对下游系统的Http接口调用关于下游系统调用逻辑需要进行提前梳理，掌握每个调用操作预期的耗时，这样做是为了能够判断消费逻辑中IO操作的耗时是否合理。<strong>通常消息堆积是由于下游系统出现了服务异常或达到了DBMS容量限制，导致消费耗时增加</strong>。</p></blockquote><blockquote><p><strong>服务异常，并不仅仅是系统中出现的类似500这样的代码错误，而可能是更加隐蔽的问题。例如，网络带宽问题。达到了DBMS容量限制(如并发读写，连接数等限制)，其也会引发消息的消费耗时增加</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306022147659.png" alt="image-20230602214754436" style="zoom:80%;"><h3 id="消费并发度" tabindex="-1">消费并发度 <a class="header-anchor" href="#消费并发度" aria-label="Permalink to &quot;消费并发度&quot;">​</a></h3><blockquote><p>一般情况下，<strong>消费者端的消费并发度由单节点线程数和节点数量共同决定，其值为单节点线程数 * 节点</strong><strong>数量。不过，通常需要优先调整单节点的线程数，若单机硬件资源达到了上限，则需要通过横向扩展</strong><strong>来提高消费并发度</strong>。</p></blockquote><blockquote><ul><li><strong>单节点线程数，即单个Consumer所包含的线程数量</strong></li><li><strong>节点数量，即Consumer Group所包含的Consumer数量</strong></li></ul></blockquote><blockquote><p>对于<strong>普通消息、延时消息及事务消息，并发度计算都是单节点线程数*节点数量</strong>。但对于顺序消息则是不同的。<strong>顺序消息的消费并发度等于Topic的Queue分区数量。</strong></p></blockquote><blockquote><p>1）<strong>全局顺序消息</strong>：<strong>该类型消息的Topic只有一个Queue分区。其可以保证该Topic的所有消息被顺序消费。为了保证这个全局顺序性，Consumer Group中在同一时刻只能有一个Consumer的一个线程进行消费。所以其并发度为1</strong>。</p></blockquote><blockquote><p>2）<strong>分区顺序消息</strong>：该类型消息的Topic有多个Queue分区。其仅可以保证该Topic的每个Queue分区中的消息被顺序消费，不能保证整个Topic中消息的顺序消费。为了保证这个分区顺序性，每个Queue分区中的消息在Consumer Group中的同一时刻只能有一个Consumer的一个线程进行消费。即，在同一时刻最多会出现多个Queue分蘖有多个Consumer的多个线程并行消费。所以其并发度为Topic的分区数量。</p></blockquote><h3 id="单机线程数计算" tabindex="-1">单机线程数计算 <a class="header-anchor" href="#单机线程数计算" aria-label="Permalink to &quot;单机线程数计算&quot;">​</a></h3><blockquote><p>对于一台主机中线程池中线程数的设置需要谨慎，不能盲目直接调大线程数，设置过大的线程数反而会 带来大量的线程切换的开销。*<em>理想环境下单节点的最优线程数计算模型为：C <em>（T1 + T2）/ T1</em></em>。</p></blockquote><blockquote><ul><li>C：<strong>CPU内核数</strong></li><li>T1：<strong>CPU内部逻辑计算耗时</strong></li><li>T2：<strong>外部IO操作耗时</strong></li></ul></blockquote><blockquote><p>最优线程数 = C *（T1 + T2）/ T1 = C * T1/T1 + C * T2/T1 = C + C * T2/T1</p></blockquote><blockquote><p>注意，<strong>该计算出的数值是理想状态下的理论数据，在生产环境中，不建议直接使用。而是根据当前环境，先设置一个比该值小的数值然后观察其压测效果，然后再根据效果逐步调大线程数，直至找到在该环境中性能最佳时的值</strong>。</p></blockquote><h3 id="如何避免" tabindex="-1">如何避免 <a class="header-anchor" href="#如何避免" aria-label="Permalink to &quot;如何避免&quot;">​</a></h3><blockquote><p>为了避免在业务使用时出现非预期的消息堆积和消费延迟问题，需要在前期设计阶段对整个业务逻辑进 行完善的排查和梳理。其中最重要的就是梳理消息的消费耗时和设置消息消费的并发度。</p></blockquote><h4 id="梳理消息的消费耗时" tabindex="-1">梳理消息的消费耗时 <a class="header-anchor" href="#梳理消息的消费耗时" aria-label="Permalink to &quot;梳理消息的消费耗时&quot;">​</a></h4><blockquote><p>通过压测获取消息的消费耗时，并对耗时较高的操作的代码逻辑进行分析。梳理消息的消费耗时需要关 注以下信息：</p></blockquote><blockquote><ul><li>消息消费逻辑的<strong>计算复杂度是否过高，代码是否存在无限循环和递归等缺陷</strong>。</li><li>消息消费逻辑中的<strong>I/O操作是否是必须的，能否用本地缓存等方案规避</strong>。</li><li>消费逻辑中的<strong>复杂耗时的操作是否可以做异步化处理。如果可以，是否会造成逻辑错乱</strong>。</li></ul></blockquote><h4 id="设置消费并发度" tabindex="-1">设置消费并发度 <a class="header-anchor" href="#设置消费并发度" aria-label="Permalink to &quot;设置消费并发度&quot;">​</a></h4><p>对于消息消费并发度的计算，可以通过以下两步实施：</p><blockquote><ul><li><p><strong>逐步调大单个Consumer节点的线程数，并观测节点的系统指标，得到单个节点最优的消费线程数</strong></p><p><strong>和消息吞吐量</strong>。</p></li><li><p>根据上下游链路的流量峰值计算出需要设置的节点数</p></li><li><p>节点数 = 流量峰值 / 单个节点消息吞吐量</p></li></ul></blockquote><h2 id="消息的清理" tabindex="-1">消息的清理 <a class="header-anchor" href="#消息的清理" aria-label="Permalink to &quot;消息的清理&quot;">​</a></h2><blockquote><p><strong>消息被消费过后会被清理掉吗？不会的</strong>。</p></blockquote><blockquote><p><strong>消息是被顺序存储在commitlog文件的，且消息大小不定长，所以消息的清理是不可能以消息为单位进</strong><strong>行清理的，而是以commitlog文件为单位进行清理的。否则会急剧下降清理效率，并实现逻辑复杂</strong>。</p></blockquote><blockquote><p><strong>commitlog文件存在一个过期时间，默认为72小时，即三天。除了用户手动清理外，在以下情况下也会被自动清理，无论文件中的消息是否被消费过</strong>：</p></blockquote><blockquote><ul><li><p><strong>文件过期，且到达清理时间点（默认为凌晨4点）后，自动清理过期文件</strong></p></li><li><p><strong>文件过期，且磁盘空间占用率已达过期清理警戒线（默认75%）后，无论是否达到清理时间点，</strong></p><p><strong>都会自动清理过期文件</strong></p></li><li><p><strong>磁盘占用率达到清理警戒线（默认85%）后，开始按照设定好的规则清理文件，无论是否过期。</strong></p></li><li><p><strong>默认会从最老的文件开始清理</strong></p></li><li><p><strong>磁盘占用率达到系统危险警戒线（默认90%）后，Broker将拒绝消息写入</strong></p></li></ul></blockquote><p>需要注意以下几点：</p><blockquote><ul><li><strong>对于RocketMQ系统来说，删除一个1G大小的文件，是一个压力巨大的IO操作</strong>。</li><li><strong>在删除过程中，系统性能会骤然下降。所以，其默认清理时间点为凌晨4点，访问量最小的时间</strong>。</li><li><strong>我们要保障磁盘空间的空闲率，不要使系统出现在其它时间点删除commitlog文件的情况</strong>。</li><li><strong>官方建议RocketMQ服务的Linux文件系统采用ext4。因为对于文件删除操作，ext4比ext3性能更好</strong></li></ul></blockquote><h1 id="rocketmq应用" tabindex="-1">RocketMQ应用 <a class="header-anchor" href="#rocketmq应用" aria-label="Permalink to &quot;RocketMQ应用&quot;">​</a></h1><h2 id="消息发送分类⭐" tabindex="-1">消息发送分类⭐ <a class="header-anchor" href="#消息发送分类⭐" aria-label="Permalink to &quot;消息发送分类⭐&quot;">​</a></h2><blockquote><p>Producer对于消息的发送方式也有多种选择，不同的方式会产生不同的系统效果。</p></blockquote><h3 id="同步发送消息" tabindex="-1">同步发送消息 <a class="header-anchor" href="#同步发送消息" aria-label="Permalink to &quot;同步发送消息&quot;">​</a></h3><blockquote><p><strong>同步发送消息是指，Producer发出⼀条消息后，会在收到MQ返回的ACK之后才发下⼀条消息。该方式</strong><strong>的消息可靠性最高，但消息发送效率太低</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011709842.png" alt="image-20230601170932732" style="zoom:80%;"><h3 id="异步发送消息" tabindex="-1">异步发送消息 <a class="header-anchor" href="#异步发送消息" aria-label="Permalink to &quot;异步发送消息&quot;">​</a></h3><blockquote><p><strong>异步发送消息是指，Producer发出消息后无需等待MQ返回ACK，直接发送下⼀条消息。该方式的消息</strong><strong>可靠性可以得到保障，消息发送效率也可以</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011709790.png" alt="image-20230601170954683" style="zoom:80%;"><h3 id="单向发送消息" tabindex="-1">单向发送消息 <a class="header-anchor" href="#单向发送消息" aria-label="Permalink to &quot;单向发送消息&quot;">​</a></h3><blockquote><p><strong>单向发送消息是指，Producer仅负责发送消息，不等待、不处理MQ的ACK。该发送方式时MQ也不返</strong><strong>回ACK。该方式的消息发送效率最高，但消息可靠性较差</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011710223.png" alt="image-20230601171029094" style="zoom:80%;"><h3 id="消息发送状态" tabindex="-1">消息发送状态 <a class="header-anchor" href="#消息发送状态" aria-label="Permalink to &quot;消息发送状态&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 消息发送的状态</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">enum</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">SendStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 发送成功</span></span>
<span class="line"><span style="color:#A6ACCD;">    SEND_OK</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 刷盘超时。当Broker设置的刷盘策略为同步刷盘时才可能出现这种异常状态。异步刷盘不会出现</span></span>
<span class="line"><span style="color:#A6ACCD;">    FLUSH_DISK_TIMEOUT</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// Slave同步超时。当Broker集群设置的Master-Slave的复制方式为同步复制时才可能出现这种异常状态。</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 异步复制不会出现</span></span>
<span class="line"><span style="color:#A6ACCD;">    FLUSH_SLAVE_TIMEOUT</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 没有可用的Slave。当Broker集群设置为Master-Slave的复制方式为同步复制时才可能出现这种异常状态。</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 异步复制不会出现</span></span>
<span class="line"><span style="color:#A6ACCD;">    SLAVE_NOT_AVAILABLE</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="依赖坐标" tabindex="-1">依赖坐标 <a class="header-anchor" href="#依赖坐标" aria-label="Permalink to &quot;依赖坐标&quot;">​</a></h3><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">&lt;!--需要与RocketMQ版本相同--&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.apache.rocketmq</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">rocketmq-client</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">4.9.5</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><h2 id="普通消息" tabindex="-1">普通消息 <a class="header-anchor" href="#普通消息" aria-label="Permalink to &quot;普通消息&quot;">​</a></h2><h3 id="同步消息" tabindex="-1">同步消息 <a class="header-anchor" href="#同步消息" aria-label="Permalink to &quot;同步消息&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">SyncProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 创建一个producer，参数为Producer Group名称</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定nameServer地址，集群就写两个，不是就写一个</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 设置当发送失败时重试发送的次数，默认为2次</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setRetryTimesWhenSendFailed</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 设置发送超时时限为5s，默认3s</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setSendMsgTimeout</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">5000</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 开启生产者</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 生产并发送100条消息</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTopic</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 为消息指定key</span></span>
<span class="line"><span style="color:#A6ACCD;">            msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setKeys</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">key-</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 同步发送消息</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 关闭producer</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031018356.png" alt="image-20230603101842160" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031019274.png" alt="image-20230603101902136" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031019449.png" alt="image-20230603101932281" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031020583.png" alt="image-20230603102019384" style="zoom:80%;"><h3 id="异步消息" tabindex="-1">异步消息 <a class="header-anchor" href="#异步消息" aria-label="Permalink to &quot;异步消息&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">AsyncProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定异步发送失败后不进行重试发送</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setRetryTimesWhenSendAsyncFailed</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定新创建的Topic的Queue数量为2，默认为4</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setDefaultTopicQueueNums</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTopicA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 异步发送。指定回调</span></span>
<span class="line"><span style="color:#A6ACCD;">                producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SendCallback</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                    </span><span style="color:#676E95;font-style:italic;">// 当producer接收到MQ发送来的ACK后就会触发该回调方法的执行</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onSuccess</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">sendResult</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onException</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                        e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// end-for</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// sleep一会儿</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 由于采用的是异步发送，所以若这里不sleep，</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 则消息还未发送就会将producer给关闭，报错</span></span>
<span class="line"><span style="color:#A6ACCD;">        TimeUnit</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SECONDS</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sleep</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031024546.png" alt="image-20230603102448388" style="zoom:80%;"><h3 id="单向消息" tabindex="-1">单向消息 <a class="header-anchor" href="#单向消息" aria-label="Permalink to &quot;单向消息&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OnewayProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 创建一个producer，参数为Producer Group名称</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 分别是Topic、Tag、Body</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">single</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 单向发送</span></span>
<span class="line"><span style="color:#A6ACCD;">            producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendOneway</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">producer shutdown</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="消息消费-1" tabindex="-1">消息消费 <a class="header-anchor" href="#消息消费-1" aria-label="Permalink to &quot;消息消费&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">SomeConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MQClientException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义一个pull消费者</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// DefaultLitePullConsumer consumer = new DefaultLitePullConsumer(&quot;cg&quot;);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义一个push消费者</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定nameServer</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定从第一条消息开始消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定消费topic与tag</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTopic</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定采用“广播模式”进行消费，默认为“集群模式”</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// consumer.setMessageModel(MessageModel.BROADCASTING);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 注册消息监听器</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 一旦broker中有了其订阅的消息就会触发该方法的执行，</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 其返回值为当前consumer消费的状态</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">MessageListenerConcurrently</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">-&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 逐条消费消息</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> msgs</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 返回消费状态：消费成功</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 开启消费者消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031036834.png" alt="image-20230603103622705" style="zoom:80%;"><h2 id="顺序消息" tabindex="-1">顺序消息 <a class="header-anchor" href="#顺序消息" aria-label="Permalink to &quot;顺序消息&quot;">​</a></h2><h3 id="什么是顺序消息" tabindex="-1">什么是顺序消息 <a class="header-anchor" href="#什么是顺序消息" aria-label="Permalink to &quot;什么是顺序消息&quot;">​</a></h3><blockquote><p><strong>顺序消息指的是，严格按照消息的发送顺序进行消费的消息(FIFO)</strong>。</p></blockquote><blockquote><p><strong>默认情况下生产者会把消息以Round Robin轮询方式发送到不同的Queue分区队列；而消费消息时会从</strong><strong>多个Queue上拉取消息，这种情况下的发送和消费是不能保证顺序的。如果将消息仅发送到同一个Queue中，消费时也只从这个Queue上拉取消息，就严格保证了消息的顺序性</strong>。</p></blockquote><h3 id="为什么需要顺序消息" tabindex="-1">为什么需要顺序消息 <a class="header-anchor" href="#为什么需要顺序消息" aria-label="Permalink to &quot;为什么需要顺序消息&quot;">​</a></h3><blockquote><p>例如，现在有<strong>TOPIC ORDER_STATUS (订单状态)</strong>，其下有4个Queue队列，该Topic中的不同消息用于 描述当前订单的不同状态。假设订单有状态： <strong>未支付、已支付、发货中、发货成功、发货失败</strong>。</p></blockquote><p>根据以上订单状态，生产者从时序上可以生成如下几个消息：</p><blockquote><p><strong>订单T0000001:未支付 --&gt; 订单T0000001:已支付 --&gt; 订单T0000001:发货中 --&gt; 订单T0000001:发货失败</strong></p></blockquote><blockquote><p><strong>消息发送到MQ中之后，Queue的选择如果采用轮询策略</strong>，消息在MQ的存储可能如下：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011712905.png" alt="image-20230601171236786" style="zoom:80%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011712375.png" alt="image-20230601171250266" style="zoom:80%;"><blockquote><p><strong>这种情况下，我们希望Consumer消费消息的顺序和我们发送是一致的，然而上述MQ的投递和消费方</strong><strong>式，我们无法保证顺序是正确的。对于顺序异常的消息，Consumer即使设置有一定的状态容错，也不</strong><strong>能完全处理好这么多种随机出现组合情况</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011713471.png" alt="image-20230601171315362" style="zoom:80%;"><blockquote><p><strong>基于上述的情况，可以设计如下方案：对于相同订单号的消息，通过一定的策略，将其放置在一个</strong><strong>Queue中，然后消费者再采用一定的策略（例如，一个线程独立处理一个queue，保证处理消息的顺序</strong><strong>性），能够保证消费的顺序性</strong>。</p></blockquote><h3 id="有序性分类" tabindex="-1">有序性分类 <a class="header-anchor" href="#有序性分类" aria-label="Permalink to &quot;有序性分类&quot;">​</a></h3><blockquote><p>根据有序范围的不同，RocketMQ可以严格地保证两种消息的有序性：<strong>分区有序与全局有序</strong>。</p></blockquote><h4 id="全局有序" tabindex="-1">全局有序 <a class="header-anchor" href="#全局有序" aria-label="Permalink to &quot;全局有序&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011714448.png" alt="image-20230601171401339" style="zoom:80%;"><blockquote><p><strong>当发送和消费参与的Queue只有一个时所保证的有序是整个Topic中消息的顺序， 称为全局有序。在创建Topic时指定Queue的数量</strong>。有三种指定方式：</p></blockquote><blockquote><p>1）在代码中创建Producer时，可以指定其自动创建的Topic的Queue数量</p><p>2）在RocketMQ可视化控制台中手动创建Topic时指定Queue数量</p><p>3）使用mqadmin命令手动创建Topic时指定Queue数量</p></blockquote><h4 id="分区有序" tabindex="-1">分区有序 <a class="header-anchor" href="#分区有序" aria-label="Permalink to &quot;分区有序&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011714463.png" alt="image-20230601171432349" style="zoom:80%;"><blockquote><p>如果有多个Queue参与，<strong>其仅可保证在该Queue分区队列上的消息顺序，则称为分区有序</strong>。</p></blockquote><blockquote><p>如何实现Queue的选择？在定义Producer时我们可以指定消息队列选择器，而这个选择器是我们自己实现了MessageQueueSelector接口定义的。</p></blockquote><blockquote><p>在定义选择器的选择算法时，一般需要使用选择key。这个选择key可以是消息key也可以是其它数据。但无论谁做选择key，都不能重复，都是唯一的。一般性的选择算法是，让选择key（或其hash值）与该Topic所包含的Queue的数量取模，其结果即为选择出的Queue的QueueId。</p></blockquote><blockquote><p>取模算法存在一个问题：不同选择key与Queue数量取模结果可能会是相同的，即不同选择key的消息可能会出现在相同的Queue，即同一个Consuemr可能会消费到不同选择key的消息。这个问题如何解决？</p></blockquote><blockquote><p>一般性的作法是，从消息中获取到选择key，对其进行判断。若是当前Consumer需要消费的消息，则直接消费，否则，什么也不做。这种做法要求选择key要能够随着消息一起被Consumer获取到。此时使用消息key作为选择key是比较好的做法。</p></blockquote><blockquote><p>以上做法会不会出现如下新的问题呢？不属于那个Consumer的消息被拉取走了，那么应该消费该消息的Consumer是否还能再消费该消息呢？同一个Queue中的消息不可能被同一个Group中的不同Consumer同时消费。所以，消费现一个Queue的不同选择key的消息的Consumer一定属于不同的Group。而不同的Group中的Consumer间的消费是相互隔离的，互不影响的。</p></blockquote><h3 id="代码实现" tabindex="-1">代码实现 <a class="header-anchor" href="#代码实现" aria-label="Permalink to &quot;代码实现&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OrderedProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 若为全局有序，则需要设置Queue数量为1</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// producer.setDefaultTopicQueueNums(1);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 为了演示简单，使用整型数作为orderId</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> orderId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TagA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 将orderId作为消息key</span></span>
<span class="line"><span style="color:#A6ACCD;">            msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setKeys</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">orderId</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">toString</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// send()的第三个参数值会传递给选择器的select()的第三个参数</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 该send()为同步发送</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageQueueSelector</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 具体的选择算法在该方法中定义</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MessageQueue</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">select</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageQueue</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">mqs</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msg</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                           </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">arg</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                    </span><span style="color:#676E95;font-style:italic;">// 以下是使用消息key作为选择的选择算法</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> keys </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getKeys</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#C792EA;">Integer</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> Integer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">valueOf</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">keys</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">                    </span><span style="color:#676E95;font-style:italic;">// 以下是使用arg作为选择key的选择算法</span></span>
<span class="line"><span style="color:#89DDFF;">                    </span><span style="color:#676E95;font-style:italic;">// Integer id = (Integer) arg;</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> index </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">%</span><span style="color:#A6ACCD;"> mqs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">size</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> mqs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">index</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">},</span><span style="color:#A6ACCD;"> orderId</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="延时消息" tabindex="-1">延时消息 <a class="header-anchor" href="#延时消息" aria-label="Permalink to &quot;延时消息&quot;">​</a></h2><h3 id="什么是延时消息" tabindex="-1">什么是延时消息 <a class="header-anchor" href="#什么是延时消息" aria-label="Permalink to &quot;什么是延时消息&quot;">​</a></h3><blockquote><p>当消息写入到Broker后，在指定的时长后才可被消费处理的消息，称为延时消息。采用RocketMQ的延时消息可以实现定时任务的功能，而无需使用定时器。典型的应用场景是，电商交易中超时未支付关闭订单的场景，12306平台订票超时未支付取消订票的场景。</p></blockquote><blockquote><p>在电商平台中，订单创建时会发送一条延迟消息。这条消息将会在30分钟后投递给后台业务系统（Consumer），后台业务系统收到该消息后会判断对应的订单是否已经完成支付。如果未完成，则取消订单，将商品再次放回到库存；如果完成支付，则忽略。</p></blockquote><blockquote><p>在12306平台中，车票预订成功后就会发送一条延迟消息。这条消息将会在45分钟后投递给后台业务系统（Consumer），后台业务系统收到该消息后会判断对应的订单是否已经完成支付。如果未完成，则取消预订，将车票再次放回到票池；如果完成支付，则忽略。</p></blockquote><h3 id="延时等级" tabindex="-1">延时等级 <a class="header-anchor" href="#延时等级" aria-label="Permalink to &quot;延时等级&quot;">​</a></h3><blockquote><p>延时消息的延迟时长不支持随意时长的延迟，是通过特定的延迟等级来指定的。延时等级定义在RocketMQ服务端的MessageStoreConfig 类中的如下变量中：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011715871.png" alt="image-20230601171549747" style="zoom:80%;"><blockquote><p>即，若指定的延时等级为3，则表示延迟时长为10s，即延迟等级是从1开始计数的。当然，如果需要自定义的延时等级，可以通过在broker加载的配置中新增如下配置（例如下面增加了1天这个等级1d）。配置文件在RocketMQ安装目录下的conf目录中。</p></blockquote><div class="language-properties"><button title="Copy Code" class="copy"></button><span class="lang">properties</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">messageDelayLevel</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h 1d</span></span></code></pre></div><h3 id="延时消息实现原理" tabindex="-1">延时消息实现原理 <a class="header-anchor" href="#延时消息实现原理" aria-label="Permalink to &quot;延时消息实现原理&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011716210.png" alt="image-20230601171638099" style="zoom:80%;"><p>具体实现方案是： 修改消息</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011716364.png" alt="image-20230601171658242" style="zoom:80%;"><blockquote><p>Producer将消息发送到Broker后，Broker会首先将消息写入到commitlog文件，然后需要将其分发到相应的consumequeue。不过，在分发之前，系统会先判断消息中是否带有延时等级。若没有，则直接正 常分发；若有则需要经历一个复杂的过程：</p></blockquote><blockquote><ul><li>修改消息的Topic为SCHEDULE_TOPIC_XXXX</li><li>根据延时等级，在consumequeue目录中SCHEDULE_TOPIC_XXXX主题下创建出相应的queueId 目录与consumequeue文件（如果没有这些目录与文件的话）。</li><li>延迟等级delayLevel与queueId的对应关系为queueId = delayLevel -1</li></ul></blockquote><blockquote><p>需要注意，在创建queueId目录时，并不是一次性地将所有延迟等级对应的目录全部创建完毕，而是用到哪个延迟等级创建哪个目录</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011717349.png" alt="image-20230601171727243" style="zoom:80%;"><blockquote><p>修改消息索引单元内容。索引单元中的Message Tag HashCode部分原本存放的是消息的Tag的Hash值。现修改为消息的投递时间。投递时间是指该消息被重新修改为原Topic后再次被写入到commitlog中的时间。<strong>投递时间 = 消息存储时间 + 延时等级时间</strong>。消息存储时间指的是消息被发送到Broker时的时间戳。</p></blockquote><blockquote><p>将消息索引写入到SCHEDULE_TOPIC_XXXX主题下相应的consumequeue中SCHEDULE_TOPIC_XXXX目录中各个延时等级Queue中的消息是如何排序的？</p></blockquote><blockquote><p>是按照消息投递时间排序的。一个Broker中同一等级的所有延时消息会被写入到consumequeue目录中SCHEDULE_TOPIC_XXXX目录下相同Queue中。即一个Queue中消息投递时间的延迟等级时间是相同的。那么投递时间就取决于于消息存储时间了。即按照消息被发送到Broker的时间进行排序的。</p></blockquote><h3 id="投递延时消息" tabindex="-1">投递延时消息 <a class="header-anchor" href="#投递延时消息" aria-label="Permalink to &quot;投递延时消息&quot;">​</a></h3><blockquote><p>Broker内部有⼀个延迟消息服务类ScheuleMessageService，其会消费SCHEDULE_TOPIC_XXXX中的消息，即按照每条消息的投递时间，将延时消息投递到⽬标Topic中。不过，在投递之前会从commitlog中将原来写入的消息再次读出，并将其原来的延时等级设置为0，即原消息变为了一条不延迟的普通消 息。然后再次将消息投递到目标Topic中。</p></blockquote><blockquote><p>ScheuleMessageService在Broker启动时，会创建并启动一个定时器TImer，用于执行相应的定时任务。系统会根据延时等级的个数，定义相应数量的TimerTask，每个TimerTask负责一个延迟等级消息的消费与投递。每个TimerTask都会检测相应Queue队列的第一条消息是否到期。</p></blockquote><blockquote><p>若第一条消息未到期，则后面的所有消息更不会到期（消息是按照投递时间排序的）；若第一条消 息到期了，则将该消息投递到目标Topic，即消费该消息。</p></blockquote><h3 id="将消息重新写入commitlog" tabindex="-1">将消息重新写入commitlog <a class="header-anchor" href="#将消息重新写入commitlog" aria-label="Permalink to &quot;将消息重新写入commitlog&quot;">​</a></h3><blockquote><p>延迟消息服务类ScheuleMessageService将延迟消息再次发送给了commitlog，并再次形成新的消息索 引条目，分发到相应Queue。这其实就是一次普通消息发送。只不过这次的消息Producer是延迟消息服务类ScheuleMessageService。</p></blockquote><h3 id="代码实现-1" tabindex="-1">代码实现 <a class="header-anchor" href="#代码实现-1" aria-label="Permalink to &quot;代码实现&quot;">​</a></h3><h4 id="生产者-1" tabindex="-1">生产者 <a class="header-anchor" href="#生产者-1" aria-label="Permalink to &quot;生产者&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">DelayProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 指定消息延迟等级为3级，即延迟10s</span></span>
<span class="line"><span style="color:#A6ACCD;">            msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setDelayTimeLevel</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 输出消息被发送的时间</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">print</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SimpleDateFormat</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;"> ,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="消费者-1" tabindex="-1">消费者 <a class="header-anchor" href="#消费者-1" aria-label="Permalink to &quot;消费者&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">OtherConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MQClientException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">MessageListenerConcurrently</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">-&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> msgs</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 输出消息被消费的时间</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">print</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SimpleDateFormat</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Date</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;"> ,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306031117310.png" alt="image-20230603111755189" style="zoom:80%;"><h2 id="事务消息" tabindex="-1">事务消息 <a class="header-anchor" href="#事务消息" aria-label="Permalink to &quot;事务消息&quot;">​</a></h2><h3 id="问题引入" tabindex="-1">问题引入 <a class="header-anchor" href="#问题引入" aria-label="Permalink to &quot;问题引入&quot;">​</a></h3><p>这里的一个需求场景是：工行用户A向建行用户B转账1万元。我们可以使用同步消息来处理该需求场景：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011718401.png" alt="image-20230601171819279" style="zoom:80%;"><blockquote><ol><li>工行系统发送一个给B增款1万元的同步消息M给Broker</li><li>消息被Broker成功接收后，向工行系统发送成功ACK</li><li>工行系统收到成功ACK后从用户A中扣款1万元</li><li>建行系统从Broker中获取到消息M</li><li>建行系统消费消息M，即向用户B中增加1万元</li></ol></blockquote><blockquote><p>这其中是有问题的：若第3步中的扣款操作失败，但消息已经成功发送到了Broker。对于MQ来说，只要消息写入成功，那么这个消息就可以被消费。此时建行系统中用户B增加了1万元。出现了数据不一致</p></blockquote><h3 id="解决思路" tabindex="-1">解决思路 <a class="header-anchor" href="#解决思路" aria-label="Permalink to &quot;解决思路&quot;">​</a></h3><blockquote><p>解决思路是，让第1、2、3步具有原子性，要么全部成功，要么全部失败。即消息发送成功后，必须要 保证扣款成功。如果扣款失败，则回滚发送成功的消息。而该思路即使用事务消息。这里要使用分布 式事务解决方案。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011910197.png" alt="image-20230601191002067" style="zoom:80%;"><p>使用事务消息来处理该需求场景：</p><blockquote><ol><li>事务管理器TM向事务协调器TC发起指令，开启全局事务</li><li>工行系统发一个给B增款1万元的事务消息M给TC</li><li>TC会向Broker发送半事务消息prepareHalf ，将消息M 预提交到Broker。此时的建行系统是看不到Broker中的消息M的</li><li>Broker会将预提交执行结果Report给TC。</li><li>如果预提交失败，则TC会向TM上报预提交失败的响应，全局事务结束；如果预提交成功，TC会 调用工行系统的回调操作，去完成工行用户A的预扣款1万元的操作</li><li>工行系统会向TC发送预扣款执行结果，即本地事务的执行状态</li><li>TC收到预扣款执行结果后，会将结果上报给TM。</li></ol></blockquote><p>预扣款执行结果存在三种可能性：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 描述本地事务执行状态</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">enum</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    COMMIT_MESSAGE</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 本地事务执行成功</span></span>
<span class="line"><span style="color:#A6ACCD;">    ROLLBACK_MESSAGE</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 本地事务执行失败</span></span>
<span class="line"><span style="color:#A6ACCD;">    UNKNOW</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 不确定，表示需要进行回查以确定本地事务的执行结果</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>TM会根据上报结果向TC发出不同的确认指令</p><blockquote><ul><li>若预扣款成功（本地事务状态为COMMIT_MESSAGE），则TM向TC发送Global Commit指令</li><li>若预扣款失败（本地事务状态为ROLLBACK_MESSAGE），则TM向TC发送Global Rollback指令</li><li>若现未知状态（本地事务状态为UNKNOW），则会触发工行系统的本地事务状态回查操作。回查操作会将回查结果，即COMMIT_MESSAGE或ROLLBACK_MESSAGE Report给TC。TC将结果上报给TM，TM会再向TC发送最终确认指令Global Commit或Global Rollback</li></ul></blockquote><p>TC在接收到指令后会向Broker与工行系统发出确认指令</p><blockquote><p>TC接收的若是Global Commit指令，则向Broker与工行系统发送Branch Commit指令。此时Broker中的消息M才可被建行系统看到；此时的工行用户A中的扣款操作才真正被确认TC接收到的若是Global Rollback指令，则向Broker与工行系统发送Branch Rollback指令。</p></blockquote><blockquote><p>此时Broker中的消息M将被撤销；工行用户A中的扣款操作将被回滚。以上方案就是为了确保消息投递与扣款操作能够在一个事务中，要成功都成功，有一个失败，则全部回滚。</p></blockquote><blockquote><p>以上方案并不是一个典型的XA模式。因为XA模式中的分支事务是异步的，而事务消息方案中的消息预提交与预扣款操作间是同步的。</p></blockquote><h3 id="基础" tabindex="-1">基础 <a class="header-anchor" href="#基础" aria-label="Permalink to &quot;基础&quot;">​</a></h3><h4 id="分布式事务" tabindex="-1">分布式事务 <a class="header-anchor" href="#分布式事务" aria-label="Permalink to &quot;分布式事务&quot;">​</a></h4><blockquote><p>对于分布式事务，通俗地说就是，一次操作由若干分支操作组成，这些分支操作分属不同应用，分布在 不同服务器上。分布式事务需要保证这些分支操作要么全部成功，要么全部失败。分布式事务与普通事 务一样，就是为了保证操作结果的一致性。</p></blockquote><h4 id="事务消息-1" tabindex="-1">事务消息 <a class="header-anchor" href="#事务消息-1" aria-label="Permalink to &quot;事务消息&quot;">​</a></h4><blockquote><p>RocketMQ提供了类似X/Open XA的分布式事务功能，通过事务消息能达到分布式事务的最终一致。XA 是一种分布式事务解决方案，一种分布式事务处理模式。</p></blockquote><h4 id="半事务消息" tabindex="-1">半事务消息 <a class="header-anchor" href="#半事务消息" aria-label="Permalink to &quot;半事务消息&quot;">​</a></h4><blockquote><p>暂不能投递的消息，发送方已经成功地将消息发送到了Broker，但是Broker未收到最终确认指令，此时 该消息被标记成“暂不能投递”状态，即不能被消费者看到。处于该种状态下的消息即半事务消息。</p></blockquote><h4 id="本地事务状态" tabindex="-1">本地事务状态 <a class="header-anchor" href="#本地事务状态" aria-label="Permalink to &quot;本地事务状态&quot;">​</a></h4><blockquote><p>Producer 回调操作执行的结果为本地事务状态，其会发送给TC，而TC会再发送给TM。TM会根据TC发 送来的本地事务状态来决定全局事务确认指令。</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 描述本地事务执行状态</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">enum</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">LocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    COMMIT_MESSAGE</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 本地事务执行成功</span></span>
<span class="line"><span style="color:#A6ACCD;">    ROLLBACK_MESSAGE</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 本地事务执行失败</span></span>
<span class="line"><span style="color:#A6ACCD;">    UNKNOW</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// 不确定，表示需要进行回查以确定本地事务的执行结果</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="消息回查" tabindex="-1">消息回查 <a class="header-anchor" href="#消息回查" aria-label="Permalink to &quot;消息回查&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011912860.png" alt="image-20230601191201732" style="zoom:80%;"><blockquote><p>消息回查，即重新查询本地事务的执行状态。本例就是重新到DB中查看预扣款操作是否执行成功。注意，消息回查不是重新执行回调操作。回调操作是进行预扣款操作，而消息回查则是查看预扣款操作执行的结果。</p></blockquote><p>引发消息回查的原因最常见的有两个：</p><blockquote><ul><li>回调操作返回UNKNWON</li><li>TC没有接收到TM的最终全局事务确认指令</li></ul></blockquote><p>RocketMQ中的消息回查设置</p><p>关于消息回查，有三个常见的属性设置。它们都在broker加载的配置文件中设置，例如：</p><blockquote><ul><li>transactionTimeout=20，TM在20秒内应将最终确认状态发送给TC，否则引发消息回查。默认60秒</li><li>transactionCheckMax=5，指定最多回查5次，超过后将丢弃消息并记录错误日志。默认15次。</li><li>transactionCheckInterval=10，指定设置的多次消息回查的时间间隔为10秒。默认为60秒。</li></ul></blockquote><h3 id="xa模式三剑客" tabindex="-1">XA模式三剑客 <a class="header-anchor" href="#xa模式三剑客" aria-label="Permalink to &quot;XA模式三剑客&quot;">​</a></h3><h4 id="xa协议" tabindex="-1">XA协议 <a class="header-anchor" href="#xa协议" aria-label="Permalink to &quot;XA协议&quot;">​</a></h4><blockquote><p>XA（Unix Transaction）是一种分布式事务解决方案，一种分布式事务处理模式，是基于XA协议的。XA协议由Tuxedo（Transaction for Unix has been Extended for Distributed Operation，分布式操作扩展之后的Unix事务系统）首先提出的，并交给X/Open组织，作为资源管理器与事务管理器的接口标准。 XA模式中有三个重要组件：TC、TM、RM。</p></blockquote><h4 id="tc" tabindex="-1">TC <a class="header-anchor" href="#tc" aria-label="Permalink to &quot;TC&quot;">​</a></h4><blockquote><p>Transaction Coordinator，<strong>事务协调者。维护全局和分支事务的状态，驱动全局事务提交或回滚</strong>。 <strong>RocketMQ中Broker充当着TC</strong>。</p></blockquote><h4 id="tm" tabindex="-1">TM <a class="header-anchor" href="#tm" aria-label="Permalink to &quot;TM&quot;">​</a></h4><blockquote><p>Transaction Manager，<strong>事务管理器。定义全局事务的范围：开始全局事务、提交或回滚全局事务。它</strong><strong>实际是全局事务的发起者</strong>。<strong>RocketMQ中事务消息的Producer充当着TM</strong>。</p></blockquote><h4 id="rm" tabindex="-1">RM <a class="header-anchor" href="#rm" aria-label="Permalink to &quot;RM&quot;">​</a></h4><blockquote><p><strong>Resource Manager，资源管理器。管理分支事务处理的资源，与TC交谈以注册分支事务和报告分支事</strong><strong>务的状态，并驱动分支事务提交或回滚。RocketMQ中事务消息的Producer及Broker均是RM</strong>。</p></blockquote><h4 id="xa模式架构" tabindex="-1">XA模式架构 <a class="header-anchor" href="#xa模式架构" aria-label="Permalink to &quot;XA模式架构&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011913804.png" alt="image-20230601191328667" style="zoom:80%;"><p>XA模式是一个典型的2PC，其执行原理如下：</p><blockquote><ol><li>TM向TC发起指令，开启一个全局事务。</li><li>根据业务要求，各个RM会逐个向TC注册分支事务，然后TC会逐个向RM发出预执行指令。</li><li>各个RM在接收到指令后会在进行本地事务预执行。</li><li>RM将预执行结果Report给TC。当然，这个结果可能是成功，也可能是失败。</li><li>TC在接收到各个RM的Report后会将汇总结果上报给TM，根据汇总结果TM会向TC发出确认指令。 若所有结果都是成功响应，则向TC发送Global Commit指令。 只要有结果是失败响应，则向TC发送Global Rollback指令。</li><li>TC在接收到指令后再次向RM发送确认指令。事务消息方案并不是一个典型的XA模式。因为XA模式中的分支事务是异步的，而事务消息方案中的消息预提交与预扣款操作间是同步的。</li></ol></blockquote><h4 id="注意事项" tabindex="-1">注意事项 <a class="header-anchor" href="#注意事项" aria-label="Permalink to &quot;注意事项&quot;">​</a></h4><blockquote><p>事务消息不支持延时消息。对于事务消息要做好幂等性检查，因为事务消息可能不止一次被消费（因为存在回滚后再提交的情况）</p></blockquote><h3 id="事务实现" tabindex="-1">事务实现 <a class="header-anchor" href="#事务实现" aria-label="Permalink to &quot;事务实现&quot;">​</a></h3><h4 id="transactionproducer" tabindex="-1">TransactionProducer <a class="header-anchor" href="#transactionproducer" aria-label="Permalink to &quot;TransactionProducer&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TransactionProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">TransactionMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">TransactionMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">tpg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         *  定义一个线程池</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param corePoolSize 线程池中核心线程数量</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param maximumPoolSize 线程池中最多线程数</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param keepAliveTime 这是一个时间。当线程池中线程数量大于核心线程数量是，</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         *                      多余空闲线程的存活时长</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param unit 时间单位</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param workQueue 临时存放任务的队列，其参数就是队列的长度</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         * @param threadFactory 线程工厂</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">         */</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">ExecutorService</span><span style="color:#A6ACCD;"> executorService </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadPoolExecutor</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                                                 TimeUnit</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SECONDS</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ArrayBlockingQueue</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Runnable</span><span style="color:#89DDFF;">&gt;(</span><span style="color:#F78C6C;">2000</span><span style="color:#89DDFF;">),</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ThreadFactory</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Thread</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">newThread</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Runnable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">r</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">Thread</span><span style="color:#A6ACCD;"> thread </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Thread</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">r</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                thread</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setName</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">client-transaction-msg-check-thread</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> thread</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 为生产者指定一个线程池</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setExecutorService</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">executorService</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 为生产者添加事务监听器</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setTransactionListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">ICBCTransactionListener</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> tags </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TTopic</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> tags</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">],</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 发送事务消息</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 第二个参数用于指定在执行本地事务时要使用的业务参数</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendMessageInTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">,null);</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">发送结果为：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> sendResult</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSendStatus</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="icbctransactionlistener" tabindex="-1">ICBCTransactionListener <a class="header-anchor" href="#icbctransactionlistener" aria-label="Permalink to &quot;ICBCTransactionListener&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">ICBCTransactionListener</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TransactionListener</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 回调操作方法</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 消息预提交成功就会触发该方法的执行，用于完成本地事务</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">executeLocalTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msg</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">arg</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">预提交消息成功：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 假设接收到TAGA的消息就表示扣款操作成功，TAGB的消息表示扣款失败，</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// TAGC表示扣款结果不清楚，需要执行消息回查</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTags</span><span style="color:#89DDFF;">()))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">COMMIT_MESSAGE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTags</span><span style="color:#89DDFF;">()))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">ROLLBACK_MESSAGE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTags</span><span style="color:#89DDFF;">()))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">UNKNOW</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">UNKNOW</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 消息回查方法</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 引发消息回查的原因最常见的有两个：</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 1)回调操作返回UNKNWON</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 2)TC没有接收到TM的最终全局事务确认指令</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">LocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">checkLocalTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msg</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">执行消息回查</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTags</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">COMMIT_MESSAGE</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="someconsumer" tabindex="-1">SomeConsumer <a class="header-anchor" href="#someconsumer" aria-label="Permalink to &quot;SomeConsumer&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">SomeConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MQClientException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义一个pull消费者</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// DefaultLitePullConsumer consumer = new DefaultLitePullConsumer(&quot;cg&quot;);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义一个push消费者</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定nameServer</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定从第一条消息开始消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定消费topic与tag</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TTopic</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定采用“广播模式”进行消费，默认为“集群模式”</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// consumer.setMessageModel(MessageModel.BROADCASTING);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 注册消息监听器</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListenerConcurrently</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 一旦broker中有了其订阅的消息就会触发该方法的执行，</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 其返回值为当前consumer消费的状态</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ConsumeConcurrentlyStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">consumeMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msgs</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                                   </span><span style="color:#C792EA;">ConsumeConcurrentlyContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 逐条消费消息</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> msgs</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 返回消费状态：消费成功</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 开启消费者消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306032113274.png" alt="image-20230603211306069" style="zoom:80%;"><h2 id="批量消息" tabindex="-1">批量消息 <a class="header-anchor" href="#批量消息" aria-label="Permalink to &quot;批量消息&quot;">​</a></h2><h3 id="批量发送消息" tabindex="-1">批量发送消息 <a class="header-anchor" href="#批量发送消息" aria-label="Permalink to &quot;批量发送消息&quot;">​</a></h3><h4 id="发送限制" tabindex="-1">发送限制 <a class="header-anchor" href="#发送限制" aria-label="Permalink to &quot;发送限制&quot;">​</a></h4><p>生产者进行消息发送时可以一次发送多条消息，这可以提升Producer的发送效率。不过需要注意以下几点：</p><blockquote><ul><li>批量发送的消息必须具有相同的Topic</li><li>批量发送的消息必须具有相同的刷盘策略</li><li>批量发送的消息不能是延时消息与事务消息</li></ul></blockquote><h4 id="批量发送大小" tabindex="-1">批量发送大小 <a class="header-anchor" href="#批量发送大小" aria-label="Permalink to &quot;批量发送大小&quot;">​</a></h4><blockquote><p>默认情况下，一批发送的消息总大小不能超过4MB字节。如果想超出该值，有两种解决方案：</p></blockquote><blockquote><ul><li>方案一：将批量消息进行拆分，拆分为若干不大于4M的消息集合分多次批量发送</li><li>方案二：在Producer端与Broker端修改属性</li></ul></blockquote><blockquote><ul><li>Producer端需要在发送之前设置Producer的maxMessageSize属性</li><li>Broker端需要修改其加载的配置文件中的maxMessageSize属性</li></ul></blockquote><h4 id="生产者发送的消息大小" tabindex="-1">生产者发送的消息大小 <a class="header-anchor" href="#生产者发送的消息大小" aria-label="Permalink to &quot;生产者发送的消息大小&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011915622.png" alt="image-20230601191531513" style="zoom:80%;"><blockquote><p>生产者通过send()方法发送的Message，并不是直接将Message序列化后发送到网络上的，而是通过这 个Message生成了一个字符串发送出去的。这个字符串由四部分构成：Topic、消息Body、消息日志 （占20字节），及用于描述消息的一堆属性key-value。这些属性中包含例如生产者地址、生产时间、 要发送的QueueId等。最终写入到Broker中消息单元中的数据都是来自于这些属性。</p></blockquote><h3 id="批量消费消息" tabindex="-1">批量消费消息 <a class="header-anchor" href="#批量消费消息" aria-label="Permalink to &quot;批量消费消息&quot;">​</a></h3><h4 id="修改批量属性" tabindex="-1">修改批量属性 <a class="header-anchor" href="#修改批量属性" aria-label="Permalink to &quot;修改批量属性&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011916630.png" alt="image-20230601191603512" style="zoom:80%;"><blockquote><p>Consumer的MessageListenerConcurrently监听接口的consumeMessage()方法的第一个参数为消息列表，但默认情况下每次只能消费一条消息。若要使其一次可以消费多条消息，则可以通过修改Consumer的consumeMessageBatchMaxSize属性来指定。不过，该值不能超过32。</p></blockquote><blockquote><p>因为默认情况下消费者每次可以拉取的消息最多是32条。若要修改一次拉取的最大值，则可通过修改Consumer的pullBatchSize属性来指定。</p></blockquote><h3 id="存在的问题" tabindex="-1">存在的问题 <a class="header-anchor" href="#存在的问题" aria-label="Permalink to &quot;存在的问题&quot;">​</a></h3><blockquote><p>Consumer的pullBatchSize属性与consumeMessageBatchMaxSize属性是否设置的越大越好？当然不是。</p></blockquote><blockquote><p>pullBatchSize值设置的越大，Consumer每拉取一次需要的时间就会越长，且在网络上传输出现问题的可能性就越高。若在拉取过程中若出现了问题，那么本批次所有消息都需要全部重新拉取。</p></blockquote><blockquote><p>consumeMessageBatchMaxSize值设置的越大，Consumer的消息并发消费能力越低，且这批被消费的消息具有相同的消费结果。因为consumeMessageBatchMaxSize指定的一批消息只会使用一个线程进行处理，且在处理过程中只要有一个消息处理异常，则这批消息需要全部重新再次消费处理。</p></blockquote><h3 id="代码实现-2" tabindex="-1">代码实现 <a class="header-anchor" href="#代码实现-2" aria-label="Permalink to &quot;代码实现&quot;">​</a></h3><h4 id="batchproducer" tabindex="-1">BatchProducer <a class="header-anchor" href="#batchproducer" aria-label="Permalink to &quot;BatchProducer&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BatchProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定要发送的消息的最大大小，默认是4M</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 不过，仅修改该属性是不行的，还需要同时修改broker加载的配置文件中的</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// maxMessageSize属性</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// producer.setMaxMessageSize(8 * 1024 * 1024);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义要发送的消息集合</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> messages </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ArrayList</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicD</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            messages</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 定义消息列表分割器，将消息列表分割为多个不超出4M大小的小列表</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">MessageListSplitter</span><span style="color:#A6ACCD;"> splitter </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListSplitter</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">messages</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">while</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">splitter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">hasNext</span><span style="color:#89DDFF;">())</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">  listItem </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> splitter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">next</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">listItem</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="messagelistsplitter" tabindex="-1">MessageListSplitter <a class="header-anchor" href="#messagelistsplitter" aria-label="Permalink to &quot;MessageListSplitter&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 消息列表分割器：其只会处理每条消息的大小不超4M的情况。</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 若存在某条消息，其本身大小大于4M，这个分割器无法处理，</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 其直接将这条消息构成一个子列表返回。并没有再进行分割</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">MessageListSplitter</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Iterator</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 指定极限值为4M</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> SIZE_LIMIT </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">4</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#F78C6C;">1024</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1024</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 存放所有要发送的消息</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 要进行批量发送消息的小集合起始索引</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> currIndex</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListSplitter</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">messages</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">this.</span><span style="color:#A6ACCD;">messages </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">hasNext</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 判断当前开始遍历的消息索引要小于消息总数</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> currIndex </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">size</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">next</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> nextIndex </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> currIndex</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 记录当前要发送的这一小批次消息列表的大小</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> totalSize </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(;</span><span style="color:#A6ACCD;"> nextIndex </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">size</span><span style="color:#89DDFF;">();</span><span style="color:#A6ACCD;"> nextIndex</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 获取当前遍历的消息</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> message </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">nextIndex</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 统计当前遍历的message的大小</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> tmpSize </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTopic</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">length</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">().</span><span style="color:#A6ACCD;">length</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> properties </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getProperties</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Map</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Entry</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> entry </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> properties</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">entrySet</span><span style="color:#89DDFF;">())</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                tmpSize </span><span style="color:#89DDFF;">+=</span><span style="color:#A6ACCD;"> entry</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getKey</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">length</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> entry</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getValue</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">length</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            tmpSize </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> tmpSize </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">20</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">// 判断当前消息本身是否大于4M</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">tmpSize </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> SIZE_LIMIT</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">nextIndex </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> currIndex </span><span style="color:#89DDFF;">==</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                    nextIndex</span><span style="color:#89DDFF;">++;</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">break</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">tmpSize </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> totalSize </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> SIZE_LIMIT</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">break</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                totalSize </span><span style="color:#89DDFF;">+=</span><span style="color:#A6ACCD;"> tmpSize</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">// end-for</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 获取当前messages列表的子集合[currIndex, nextIndex)</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> subList </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subList</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">currIndex</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> nextIndex</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 下次遍历的开始索引</span></span>
<span class="line"><span style="color:#A6ACCD;">        currIndex </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> nextIndex</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> subList</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="batchconsumer" tabindex="-1">BatchConsumer <a class="header-anchor" href="#batchconsumer" aria-label="Permalink to &quot;BatchConsumer&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BatchConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MQClientException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicD</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定每次可以消费10条消息，默认为1</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeMessageBatchMaxSize</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">10</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定每次可以从Broker拉取40条消息，默认为32</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setPullBatchSize</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">40</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListenerConcurrently</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ConsumeConcurrentlyStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">consumeMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msgs</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                             </span><span style="color:#C792EA;">ConsumeConcurrentlyContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;"> msgs</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 消费成功的返回结果</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 消费异常时的返回结果</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// return ConsumeConcurrentlyStatus.RECONSUME_LATER;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="消息过滤" tabindex="-1">消息过滤 <a class="header-anchor" href="#消息过滤" aria-label="Permalink to &quot;消息过滤&quot;">​</a></h2><blockquote><p>消息者在进行消息订阅时，除了可以指定要订阅消息的Topic外，还可以对指定Topic中的消息根据指定 条件进行过滤，即可以订阅比Topic更加细粒度的消息类型。对于指定Topic消息的过滤有两种过滤方式：Tag过滤与SQL过滤。</p></blockquote><h3 id="tag过滤" tabindex="-1">Tag过滤 <a class="header-anchor" href="#tag过滤" aria-label="Permalink to &quot;Tag过滤&quot;">​</a></h3><blockquote><p>通过consumer的subscribe()方法指定要订阅消息的Tag。如果订阅多个Tag的消息，Tag间使用或运算符(双竖线||)连接。</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">FilterByTagProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 发送的消息均包含Tag，为以下三种Tag之一</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> tags </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTagA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTagB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTagC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> tag </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">   tags</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i </span><span style="color:#89DDFF;">%</span><span style="color:#A6ACCD;"> tags</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">length</span><span style="color:#89DDFF;">];</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> tag</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">FilterByTagConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 仅订阅Tag为myTagA与myTagB的消息，不包含myTagC</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTagA || myTagB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListenerConcurrently</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ConsumeConcurrentlyStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">consumeMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msgs</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                               </span><span style="color:#C792EA;">ConsumeConcurrentlyContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> me</span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">                    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">me</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="sql过滤" tabindex="-1">SQL过滤 <a class="header-anchor" href="#sql过滤" aria-label="Permalink to &quot;SQL过滤&quot;">​</a></h3><blockquote><p>SQL过滤是一种通过特定表达式对事先埋入到消息中的用户属性进行筛选过滤的方式。通过SQL过滤，可以实现对消息的复杂过滤。不过，只有使用PUSH模式的消费者才能使用SQL过滤。SQL过滤表达式中支持多种常量类型与运算符。</p></blockquote><blockquote><p>支持的常量类型：</p><ul><li>数值：比如：123，3.1415</li><li>字符：必须用单引号包裹起来，比如：&#39;abc&#39;</li><li>布尔：TRUE 或 FALSE</li><li>NULL：特殊的常量，表示空</li></ul></blockquote><blockquote><p>支持的运算符有：</p><ul><li>数值比较：&gt;，&gt;=，&lt;，&lt;=，BETWEEN，=</li><li>字符比较：=，&lt;&gt;，IN</li><li>逻辑运算 ：AND，OR，NOT</li><li>NULL判断：IS NULL 或者 IS NOT NULL</li></ul></blockquote><blockquote><p>默认情况下Broker没有开启消息的SQL过滤功能，需要在Broker加载的配置文件中添加如下属性，以开 启该功能：</p></blockquote><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">enablePropertyFilter  = true</span></span></code></pre></div><blockquote><p>在启动Broker时需要指定这个修改过的配置文件。例如对于单机Broker的启动，其修改的配置文件是 conf/broker.conf，启动时使用如下命令：</p></blockquote><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">sh bin/mqbroker -n localhost:9876 -c conf/broker.conf &amp;</span></span></code></pre></div><blockquote><p>代码实现</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">FilterBySQLProducer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hi,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">getBytes</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Message</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicE</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myTag</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 事先埋入用户属性age</span></span>
<span class="line"><span style="color:#A6ACCD;">                msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">putUserProperty</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sendResult </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                e</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printStackTrace</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">shutdown</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">FilterBySQLConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Exception</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">192.168.88.101:9876;192.168.88.102:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 要从TopicE的消息中过滤出age在[0, 6]间的消息</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TopicE</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> MessageSelector</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">bySql</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">age between 0 and 6</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">((</span><span style="color:#A6ACCD;">MessageListenerConcurrently</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">-&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> me</span><span style="color:#89DDFF;font-style:italic;">:</span><span style="color:#A6ACCD;">msgs</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">                System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">me</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="消息发送重试机制" tabindex="-1">消息发送重试机制 <a class="header-anchor" href="#消息发送重试机制" aria-label="Permalink to &quot;消息发送重试机制&quot;">​</a></h2><h3 id="说明" tabindex="-1">说明 <a class="header-anchor" href="#说明" aria-label="Permalink to &quot;说明&quot;">​</a></h3><blockquote><p>Producer对发送失败的消息进行重新发送的机制，称为消息发送重试机制，也称为消息重投机制。</p></blockquote><p>对于消息重投，需要注意以下几点：</p><blockquote><p>生产者在发送消息时，若采用同步或异步发送方式，发送失败会重试，但oneway消息发送方式发送失败是没有重试机制的</p><p>只有普通消息具有发送重试机制，顺序消息是没有的</p><p>消息重投机制可以保证消息尽可能发送成功、不丢失，但可能会造成消息重复。消息重复在RocketMQ中是无法避免的问题</p><p>消息重复在一般情况下不会发生，当出现消息量大、网络抖动，消息重复就会成为大概率事件producer主动重发、consumer负载变化（发生Rebalance，不会导致消息重复，但可能出现重复消费）也会导致重复消息</p><p>消息重复无法避免，但要避免消息的重复消费。</p><p>避免消息重复消费的解决方案是，为消息添加唯一标识（例如消息key），使消费者对消息进行消费判断来避免重复消费</p><p>消息发送重试有三种策略可以选择：同步发送失败策略、异步发送失败策略、消息刷盘失败策略</p></blockquote><h3 id="同步发送失败策略" tabindex="-1">同步发送失败策略 <a class="header-anchor" href="#同步发送失败策略" aria-label="Permalink to &quot;同步发送失败策略&quot;">​</a></h3><blockquote><p>对于普通消息，消息发送默认采用round-robin策略来选择所发送到的队列。如果发送失败，默认重试2 次。但在重试时是不会选择上次发送失败的Broker，而是选择其它Broker。当然，若只有一个Broker其 也只能发送到该Broker，但其会尽量发送到该Broker上的其它Queue。</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 创建一个producer，参数为Producer Group名称</span></span>
<span class="line"><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 指定nameServer地址</span></span>
<span class="line"><span style="color:#A6ACCD;">producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">rocketmqOS:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 设置同步发送失败时重试发送的次数，默认为2次</span></span>
<span class="line"><span style="color:#A6ACCD;">producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setRetryTimesWhenSendFailed</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 设置发送超时时限为5s，默认3s</span></span>
<span class="line"><span style="color:#A6ACCD;">producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setSendMsgTimeout</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">5000</span><span style="color:#89DDFF;">);</span></span></code></pre></div><blockquote><p>同时，Broker还具有失败隔离功能，使Producer尽量选择未发生过发送失败的Broker作为目标Broker。其可以保证其它消息尽量不发送到问题Broker，为了提升消息发送效率，降低消息发送耗时。</p></blockquote><p>思考：让我们自己实现失败隔离功能，如何来做？</p><blockquote><p>方案一：Producer中维护某JUC的Map集合，其key是发生失败的时间戳，value为Broker实例。Producer中还维护着一个Set集合，其中存放着所有未发生发送异常的Broker实例。选择目标Broker是从该Set集合中选择的。再定义一个定时任务，定期从Map集合中将长期未发生发送异常的Broker清理出去，并添加到Set集合。</p></blockquote><blockquote><p>方案二：为Producer中的Broker实例添加一个标识，例如是一个AtomicBoolean属性。只要该 Broker上发生过发送异常，就将其置为true。选择目标Broker就是选择该属性值为false的Broker。再定义一个定时任务，定期将Broker的该属性置为false。</p></blockquote><blockquote><p>方案三：为Producer中的Broker实例添加一个标识，例如是一个AtomicLong属性。只要该 Broker上发生过发送异常，就使其值增一。选择目标Broker就是选择该属性值最小的Broker。若 该值相同，采用轮询方式选择。</p></blockquote><blockquote><p>如果超过重试次数，则抛出异常，由Producer去保证消息不丢。当然当生产者出现 RemotingException、MQClientException和MQBrokerException时，Producer会自动重投消息。</p></blockquote><h3 id="异步发送失败策略" tabindex="-1">异步发送失败策略 <a class="header-anchor" href="#异步发送失败策略" aria-label="Permalink to &quot;异步发送失败策略&quot;">​</a></h3><p>异步发送失败重试时，异步重试不会选择其他broker，仅在同一个broker上做重试，所以该策略无法保 证消息不丢。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">DefaultMQProducer</span><span style="color:#A6ACCD;"> producer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQProducer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">pg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">rocketmqOS:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 指定异步发送失败后不进行重试发送</span></span>
<span class="line"><span style="color:#A6ACCD;">producer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setRetryTimesWhenSendAsyncFailed</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">);</span></span></code></pre></div><h3 id="消息刷盘失败策略" tabindex="-1">消息刷盘失败策略 <a class="header-anchor" href="#消息刷盘失败策略" aria-label="Permalink to &quot;消息刷盘失败策略&quot;">​</a></h3><blockquote><p>消息刷盘超时（Master或Slave）或slave不可用（slave在做数据同步时向master返回状态不SEND_OK）时，默认是不会将消息尝试发送到其他Broker的。不过，对于重要消息可以通过在Broker 的配置文件设置retryAnotherBrokerWhenNotStoreOK属性为true来开启。</p></blockquote><h2 id="消息消费重试机制" tabindex="-1">消息消费重试机制 <a class="header-anchor" href="#消息消费重试机制" aria-label="Permalink to &quot;消息消费重试机制&quot;">​</a></h2><h3 id="顺序消息的消费重试" tabindex="-1">顺序消息的消费重试 <a class="header-anchor" href="#顺序消息的消费重试" aria-label="Permalink to &quot;顺序消息的消费重试&quot;">​</a></h3><blockquote><p>对于顺序消息，当Consumer消费消息失败后，为了保证消息的顺序性，其会自动不断地进行消息重试，直到消费成功。消费重试默认间隔时间为1000毫秒。重试期间应用会出现消息消费被阻塞的情况。</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 顺序消息消费失败的消费重试时间间隔，单位毫秒，默认为1000，其取值范围为[10,30000]</span></span>
<span class="line"><span style="color:#A6ACCD;">consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setSuspendCurrentQueueTimeMillis</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">);</span></span></code></pre></div><h3 id="无序消息的消费重试" tabindex="-1">无序消息的消费重试 <a class="header-anchor" href="#无序消息的消费重试" aria-label="Permalink to &quot;无序消息的消费重试&quot;">​</a></h3><blockquote><p>对于无序消息（普通消息、延时消息、事务消息），当Consumer消费消息失败时，可以通过设置返回状态达到消息重试的效果。不过需要注意，无序消息的重试只对集群消费方式生效，广播消费方式不提供失败重试特性。即对于广播消费，消费失败后，失败消息不再重试，继续消费后续消息。</p></blockquote><h3 id="消费重试次数与间隔" tabindex="-1">消费重试次数与间隔 <a class="header-anchor" href="#消费重试次数与间隔" aria-label="Permalink to &quot;消费重试次数与间隔&quot;">​</a></h3><blockquote><p>对于无序消息集群消费下的重试消费，每条消息默认最多重试16次，但每次重试的间隔时间是不同 的，会逐渐变长。每次重试的间隔时间如下表。</p></blockquote><p>「另外消息不是一直重试，而是每隔1段时间进行重试」</p><table><thead><tr><th style="text-align:left;">第几次重试</th><th style="text-align:left;">与上次重试的间隔时间</th><th style="text-align:left;">第几次重试</th><th style="text-align:left;">与上次重试的间隔时间</th></tr></thead><tbody><tr><td style="text-align:left;">1</td><td style="text-align:left;">10 秒</td><td style="text-align:left;">9</td><td style="text-align:left;">7 分钟</td></tr><tr><td style="text-align:left;">2</td><td style="text-align:left;">30 秒</td><td style="text-align:left;">10</td><td style="text-align:left;">8 分钟</td></tr><tr><td style="text-align:left;">3</td><td style="text-align:left;">1 分钟</td><td style="text-align:left;">11</td><td style="text-align:left;">9 分钟</td></tr><tr><td style="text-align:left;">4</td><td style="text-align:left;">2 分钟</td><td style="text-align:left;">12</td><td style="text-align:left;">10 分钟</td></tr><tr><td style="text-align:left;">5</td><td style="text-align:left;">3 分钟</td><td style="text-align:left;">13</td><td style="text-align:left;">20 分钟</td></tr><tr><td style="text-align:left;">6</td><td style="text-align:left;">4 分钟</td><td style="text-align:left;">14</td><td style="text-align:left;">30 分钟</td></tr><tr><td style="text-align:left;">7</td><td style="text-align:left;">5 分钟</td><td style="text-align:left;">15</td><td style="text-align:left;">1 小时</td></tr><tr><td style="text-align:left;">8</td><td style="text-align:left;">6 分钟</td><td style="text-align:left;">16</td><td style="text-align:left;">2 小时</td></tr></tbody></table><blockquote><p>若一条消息在一直消费失败的前提下，将会在正常消费后的第4小时46分后进行第16次重试。 若仍然失败，则将消息投递到死信队列，修改消费重试次数</p></blockquote><blockquote><p>对于修改过的重试次数，将按照以下策略执行：</p></blockquote><blockquote><ul><li>若修改值小于16，则按照指定间隔进行重试</li><li>若修改值大于16，则超过16次的重试时间间隔均为2小时</li><li>对于Consumer Group，若仅修改了一个Consumer的消费重试次数，则会应用到该Group中所有其它Consumer实例。若出现多个Consumer均做了修改的情况，则采用覆盖方式生效。即最后被修改的值会覆盖前面设置的值。</li></ul></blockquote><h3 id="重试队列-1" tabindex="-1">重试队列 <a class="header-anchor" href="#重试队列-1" aria-label="Permalink to &quot;重试队列&quot;">​</a></h3><blockquote><p>对于需要重试消费的消息，并不是Consumer在等待了指定时长后再次去拉取原来的消息进行消费，而 是将这些需要重试消费的消息放入到了一个特殊Topic的队列中，而后进行再次消费的。这个特殊的队 列就是重试队列。</p></blockquote><blockquote><p>当出现需要进行重试消费的消息时，Broker会为每个消费组都设置一个Topic名称为%RETRY%consumerGroup@consumerGroup 的重试队列。</p></blockquote><blockquote><ul><li>这个重试队列是针对消息才组的，而不是针对每个Topic设置的（一个Topic的消息可以让多个消费者组进行消费，所以会为这些消费者组各创建一个重试队列）</li><li>只有当出现需要进行重试消费的消息时，才会为该消费者组创建重试队列</li></ul></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011922736.png" alt="image-20230601192247583" style="zoom:80%;"><blockquote><p>注意，消费重试的时间间隔与延时消费的延时等级十分相似，除了没有延时等级的前两个时间外，其它的时间都是相同的Broker对于重试消息的处理是通过延时消息实现的。先将消息保存到SCHEDULE_TOPIC_XXXX延迟队列中，延迟时间到后，会将消息投递到%RETRY %consumerGroup@consumerGroup重试队列中。</p></blockquote><h3 id="消费重试配置方式" tabindex="-1">消费重试配置方式 <a class="header-anchor" href="#消费重试配置方式" aria-label="Permalink to &quot;消费重试配置方式&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011923035.png" alt="image-20230601192308910" style="zoom:80%;"><p>集群消费方式下，消息消费失败后若希望消费重试，则需要在消息监听器接口的实现中明确进行如下三 种方式之一的配置：</p><blockquote><p>方式1：返回ConsumeConcurrentlyStatus.RECONSUME_LATER（推荐） 方式2：返回Null 方式3：抛出异常</p></blockquote><h3 id="消费不重试配置方式" tabindex="-1">消费不重试配置方式 <a class="header-anchor" href="#消费不重试配置方式" aria-label="Permalink to &quot;消费不重试配置方式&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202306011923072.png" alt="image-20230601192341929" style="zoom:80%;"><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RetryConsumer</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">static</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">main</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">args</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MQClientException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">DefaultMQPushConsumer</span><span style="color:#A6ACCD;"> consumer </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">DefaultMQPushConsumer</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cg</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setNamesrvAddr</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">mqOS:9876</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setConsumeFromWhere</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">ConsumeFromWhere</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_FROM_FIRST_OFFSET</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someTopic</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">registerMessageListener</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">MessageListenerConcurrently</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ConsumeConcurrentlyStatus</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">consumeMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msgs</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                                            </span><span style="color:#C792EA;">ConsumeConcurrentlyContext</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">context</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">               </span><span style="color:#89DDFF;font-style:italic;">try</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">                   </span><span style="color:#676E95;font-style:italic;">// ....</span></span>
<span class="line"><span style="color:#A6ACCD;">               </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">catch</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">e</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                   </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">               </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">                </span><span style="color:#676E95;font-style:italic;">// 返回消费状态：消费成功</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> ConsumeConcurrentlyStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">CONSUME_SUCCESS</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 开启消费者消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumer</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">printf</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Consumer Started.%n</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><blockquote><p>集群消费方式下，消息消费失败后若不希望消费重试，则在捕获到异常后同样也返回与消费成功后的相 同的结果，即ConsumeConcurrentlyStatus.CONSUME_SUCCESS，则不进行消费重试。</p></blockquote><h2 id="死信队列" tabindex="-1">死信队列 <a class="header-anchor" href="#死信队列" aria-label="Permalink to &quot;死信队列&quot;">​</a></h2><h3 id="什么是死信队列" tabindex="-1">什么是死信队列 <a class="header-anchor" href="#什么是死信队列" aria-label="Permalink to &quot;什么是死信队列&quot;">​</a></h3><blockquote><p>当一条消息初次消费失败，消息队列会自动进行消费重试；达到最大重试次数后，若消费依然失败，则 表明消费者在正常情况下无法正确地消费该消息，此时，消息队列不会立刻将消息丢弃，而是将其发送 到该消费者对应的特殊队列中。这个队列就是死信队列（Dead-Letter Queue，DLQ），而其中的消息 则称为死信消息（Dead-Letter Message，DLM）。死信队列是用于处理无法被正常消费的消息的。</p></blockquote><h3 id="死信队列的特征" tabindex="-1">死信队列的特征 <a class="header-anchor" href="#死信队列的特征" aria-label="Permalink to &quot;死信队列的特征&quot;">​</a></h3><p>死信队列具有如下特征：</p><blockquote><ul><li>死信队列中的消息不会再被消费者正常消费，即DLQ对于消费者是不可见的</li><li>死信存储有效期与正常消息相同，均为 3 天（commitlog文件的过期时间），3 天后会被自动删除</li><li>死信队列就是一个特殊的Topic，名称为%DLQ%consumerGroup@consumerGroup ，即每个消费者组都有一个死信队列</li><li>如果⼀个消费者组未产生死信消息，则不会为其创建相应的死信队列</li></ul></blockquote><h3 id="死信消息的处理" tabindex="-1">死信消息的处理 <a class="header-anchor" href="#死信消息的处理" aria-label="Permalink to &quot;死信消息的处理&quot;">​</a></h3><blockquote><p>实际上，当⼀条消息进入死信队列，就意味着系统中某些地方出现了问题，从而导致消费者无法正常消 费该消息，比如代码中原本就存在Bug。因此，对于死信消息，通常需要开发人员进行特殊处理。最关 键的步骤是要排查可疑因素，解决代码中可能存在的Bug，然后将原来的死信消息再次进行投递消费</p></blockquote><h1 id="rocketmq-spring-boot-推荐" tabindex="-1">rocketmq-spring-boot(推荐) <a class="header-anchor" href="#rocketmq-spring-boot-推荐" aria-label="Permalink to &quot;rocketmq-spring-boot(推荐)&quot;">​</a></h1><h2 id="依赖坐标-1" tabindex="-1">依赖坐标 <a class="header-anchor" href="#依赖坐标-1" aria-label="Permalink to &quot;依赖坐标&quot;">​</a></h2><div class="language-xml"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">&lt;!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter --&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">org.apache.rocketmq</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">groupId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">rocketmq-spring-boot-starter</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">artifactId</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">2.2.2</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">version</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">dependency</span><span style="color:#89DDFF;">&gt;</span></span></code></pre></div><div class="language-yml"><button title="Copy Code" class="copy"></button><span class="lang">yml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">rocketmq</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F07178;">name-server</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">192.168.22.130:9876</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F07178;">producer</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">group</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">myGroup</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;"># 设置发送超时时限为5s，默认3s</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">send-message-timeout</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5000</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;"># 设置同步发送失败时重试发送的次数，默认为2次</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">retry-times-when-send-failed</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">max-message-size</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10485760</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;"># 异步失败重试，一般设置为0就行</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">retry-times-when-send-async-failed</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span></span></code></pre></div><p><code>注意：发送异步消息时不要使用@Test，会出现显示消息的情况，可以直接进行访问测试</code></p><div class="language-yml"><button title="Copy Code" class="copy"></button><span class="lang">yml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">rocketmq</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">consumer</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">group</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">springboot_consumer_group</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 一次拉取消息最大值，注意是拉取消息的最大值而非消费最大值</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">pull-batch-size</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">name-server</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">10.5.103.6:9876</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F07178;">producer</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 发送同一类消息的设置为同一个group，保证唯一</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">group</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">springboot_producer_group</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 发送消息超时时间，默认3000</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">sendMessageTimeout</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10000</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 发送消息失败重试次数，默认2</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">retryTimesWhenSendFailed</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 异步消息重试此处，默认2</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">retryTimesWhenSendAsyncFailed</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 消息最大长度，默认1024 * 1024 * 4(默认4M)</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">maxMessageSize</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">4096</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 压缩消息阈值，默认4k(1024 * 4)</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">compressMessageBodyThreshold</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">4096</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;"># 是否在内部发送失败时重试另一个broker，默认false</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#F07178;">retryNextServer</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FF9CAC;">false</span></span></code></pre></div><h2 id="问题解决" tabindex="-1">问题解决 <a class="header-anchor" href="#问题解决" aria-label="Permalink to &quot;问题解决&quot;">​</a></h2><p>以下是一些在SpringBoot中使用RocketMQ时常遇到的问题，现在为您逐一解决。</p><h3 id="warn-no-appenders-could-be-found-for-logger" tabindex="-1">WARN No appenders could be found for logger <a class="header-anchor" href="#warn-no-appenders-could-be-found-for-logger" aria-label="Permalink to &quot;WARN No appenders could be found for logger&quot;">​</a></h3><p>启动项目时会在日志中看到如下告警</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).</span></span>
<span class="line"><span style="color:#A6ACCD;">RocketMQLog:WARN Please initialize the logger system properly.</span></span></code></pre></div><p>此时我们只需要在启动类中设置环境变量 <code>rocketmq.client.logUseSlf4j</code> 为 true 明确指定RocketMQ的日志框架</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@SpringBootApplication</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketDemoApplication {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public static void main(String[] args) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        /*</span></span>
<span class="line"><span style="color:#A6ACCD;">         * 指定使用的日志框架，否则将会告警</span></span>
<span class="line"><span style="color:#A6ACCD;">         * RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).</span></span>
<span class="line"><span style="color:#A6ACCD;">         * RocketMQLog:WARN Please initialize the logger system properly.</span></span>
<span class="line"><span style="color:#A6ACCD;">         */</span></span>
<span class="line"><span style="color:#A6ACCD;">        System.setProperty(&quot;rocketmq.client.logUseSlf4j&quot;, &quot;true&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span></span>
<span class="line"><span style="color:#A6ACCD;">        SpringApplication.run(RocketDemoApplication.class, args);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>同时还得在配置文件中调整日志级别，不然在控制台会一直看到broker的日志信息</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">logging:</span></span>
<span class="line"><span style="color:#A6ACCD;"> level:</span></span>
<span class="line"><span style="color:#A6ACCD;">   RocketmqClient: ERROR</span></span>
<span class="line"><span style="color:#A6ACCD;">    io:</span></span>
<span class="line"><span style="color:#A6ACCD;">     netty: ERROR</span></span></code></pre></div><h3 id="不支持localdate-和-localdatetime" tabindex="-1">不支持LocalDate 和 LocalDateTime <a class="header-anchor" href="#不支持localdate-和-localdatetime" aria-label="Permalink to &quot;不支持LocalDate 和 LocalDateTime&quot;">​</a></h3><p>在使用Java8后经常会使用<code>LocalDate/LocalDateTime</code>这两个时间类型字段，然而RocketMQ原始配置并不支持Java时间类型，当我们发送的实体消息中包含上述两个字段时，消费端在消费时会出现如下所示的错误。</p><p>比如生产者的代码如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@GetMapping(&quot;/test&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">public void test(){</span></span>
<span class="line"><span style="color:#A6ACCD;">  //普通消息无返回值，只负责发送消息⽽不等待服务器回应且没有回调函数触发。</span></span>
<span class="line"><span style="color:#A6ACCD;">  RocketMessage rocketMessage = RocketMessage.builder().</span></span>
<span class="line"><span style="color:#A6ACCD;">    id(1111L).</span></span>
<span class="line"><span style="color:#A6ACCD;">    message(&quot;hello,world&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">    .localDate(LocalDate.now())</span></span>
<span class="line"><span style="color:#A6ACCD;">    .localDateTime(LocalDateTime.now())</span></span>
<span class="line"><span style="color:#A6ACCD;">    .build();</span></span>
<span class="line"><span style="color:#A6ACCD;">  rocketmqTemplate.convertAndSend(destination,rocketMessage);</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>消费者的代码如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">@RocketMQMessageListener(consumerGroup = &quot;springboot_consumer_group&quot;,topic = &quot;consumer_topic&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketMQConsumer implements RocketMQListener&lt;RocketMessage&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void onMessage(RocketMessage message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        System.out.println(&quot;消费消息-&quot; + message);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>消费者开始消费时会出现类型转换异常错误<code>Cannot construct instance of java.time.LocalDate</code>，错误详情如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PxMzT0Oibf4hOW85NuRu9P9pIibJib7ibEZZQgicJVZHydxAr90icXWQ9MeILm1yvnpuTgIZibvwcDNLaPqVkqtgt1tEw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>原因：RocketMQ内置使用的转换器是<strong>RocketMQMessageConverter</strong>，转换Json时使用的是MappingJackson2MessageConverter，但是这个转换器不支持时间类型。</p><p>解决办法：需要自定义消息转换器，将MappingJackson2MessageConverter进行替换，并添加支持时间模块</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Configuration</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketMQEnhanceConfig {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 解决RocketMQ Jackson不支持Java时间类型配置</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 源码参考：{@link org.apache.rocketmq.spring.autoconfigure.MessageConverterConfiguration}</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Primary</span></span>
<span class="line"><span style="color:#A6ACCD;">    public RocketMQMessageConverter enhanceRocketMQMessageConverter(){</span></span>
<span class="line"><span style="color:#A6ACCD;">        RocketMQMessageConverter converter = new RocketMQMessageConverter();</span></span>
<span class="line"><span style="color:#A6ACCD;">        CompositeMessageConverter compositeMessageConverter = (CompositeMessageConverter) converter.getMessageConverter();</span></span>
<span class="line"><span style="color:#A6ACCD;">        List&lt;MessageConverter&gt; messageConverterList = compositeMessageConverter.getConverters();</span></span>
<span class="line"><span style="color:#A6ACCD;">        for (MessageConverter messageConverter : messageConverterList) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            if(messageConverter instanceof MappingJackson2MessageConverter){</span></span>
<span class="line"><span style="color:#A6ACCD;">                MappingJackson2MessageConverter jackson2MessageConverter = (MappingJackson2MessageConverter) messageConverter;</span></span>
<span class="line"><span style="color:#A6ACCD;">                ObjectMapper objectMapper = jackson2MessageConverter.getObjectMapper();</span></span>
<span class="line"><span style="color:#A6ACCD;">                objectMapper.registerModules(new JavaTimeModule());</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        return converter;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><h3 id="rockemq环境隔离" tabindex="-1">RockeMQ环境隔离 <a class="header-anchor" href="#rockemq环境隔离" aria-label="Permalink to &quot;RockeMQ环境隔离&quot;">​</a></h3><p>在使用RocketMQ时，通常会在代码中直接指定消息主题(topic)，而且开发环境和测试环境可能共用一个RocketMQ环境。如果没有进行处理，在开发环境发送的消息就可能被测试环境的消费者消费，测试环境发送的消息也可能被开发环境的消费者消费，从而导致数据混乱的问题。</p><p>为了解决这个问题，我们可以根据不同的环境实现自动隔离。通过简单配置一个选项，如dev、test、prod等不同环境，所有的消息都会被自动隔离。例如，当发送的消息主题为<code>consumer_topic</code>时，可以自动在topic后面加上环境后缀，如<code>consumer_topic_dev</code>。</p><p>那么，我们该如何实现呢？</p><p>可以编写一个配置类实现BeanPostProcessor，并重写postProcessBeforeInitialization方法，在监听器实例初始化前修改对应的topic。</p><blockquote><p>BeanPostProcessor是Spring框架中的一个接口，它的作用是在Spring容器实例化、配置完bean之后，在bean初始化前后进行一些额外的处理工作。</p><p>具体来说，BeanPostProcessor接口定义了两个方法：</p><ul><li>postProcessBeforeInitialization(Object bean, String beanName): 在bean初始化之前进行处理，可以对bean做一些修改等操作。</li><li>postProcessAfterInitialization(Object bean, String beanName): 在bean初始化之后进行处理，可以进行一些清理或者其他操作。</li></ul><p>BeanPostProcessor可以在应用程序中对Bean的创建和初始化过程进行拦截和修改，对Bean的生命周期进行干预和操作。它可以对所有的Bean类实例进行增强处理，使得开发人员可以在Bean初始化前后自定义一些操作，从而实现自己的业务需求。比如，可以通过BeanPostProcessor来实现注入某些必要的属性值、加入某一个对象等等。</p></blockquote><p>实现方案如下：</p><ol><li>在配置文件中增加相关配置</li></ol><div class="language-yml"><button title="Copy Code" class="copy"></button><span class="lang">yml</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F07178;">rocketmq</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">enhance</span><span style="color:#89DDFF;">:</span></span>
<span class="line"><span style="color:#89DDFF;">   </span><span style="color:#676E95;font-style:italic;"># 启动隔离，用于激活配置类EnvironmentIsolationConfig</span></span>
<span class="line"><span style="color:#89DDFF;">   </span><span style="color:#676E95;font-style:italic;"># 启动后会自动在topic上拼接激活的配置文件，达到自动隔离的效果</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F07178;">enabledIsolation</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#FF9CAC;">true</span></span>
<span class="line"><span style="color:#89DDFF;">   </span><span style="color:#676E95;font-style:italic;"># 隔离环境名称，拼接到topic后，topic_dev，默认空字符串</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F07178;">environment</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">dev</span></span></code></pre></div><ol><li>新增配置类，在实例化消息监听者之前把topic修改掉</li></ol><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Configuration</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">EnvironmentIsolationConfig</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">BeanPostProcessor</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${rocketmq.enhance.enabledIsolation:true}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">boolean</span><span style="color:#A6ACCD;"> enabledIsolation</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Value</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">${rocketmq.enhance.environment:&#39;&#39;}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> environmentName</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">    /**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     * 在装载Bean之前实现参数修改</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">postProcessBeforeInitialization</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">bean</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                              </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">beanName</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">throws</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">BeansException</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">bean </span><span style="color:#89DDFF;">instanceof</span><span style="color:#A6ACCD;"> DefaultRocketMQListenerContainer</span><span style="color:#89DDFF;">){</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">DefaultRocketMQListenerContainer</span><span style="color:#A6ACCD;"> container </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DefaultRocketMQListenerContainer</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> bean</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">       </span><span style="color:#676E95;font-style:italic;">//拼接Topic</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">enabledIsolation </span><span style="color:#89DDFF;">&amp;&amp;</span><span style="color:#A6ACCD;"> StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">hasText</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">environmentName</span><span style="color:#89DDFF;">)){</span></span>
<span class="line"><span style="color:#A6ACCD;">                container</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">setTopic</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">String</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">join</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">_</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                               container</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getTopic</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;">environmentName</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> container</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> bean</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><ol><li>启动项目可以看到日志中消息监听的队列已经被修改了</li></ol><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">2023-03-23 17:04:59.726 [main] INFO  o.a.r.s.support.DefaultRocketMQListenerContainer:290 - running container: DefaultRocketMQListenerContainer{consumerGroup=&#39;springboot_consumer_group&#39;, nameServer=&#39;10.5.103.6:9876&#39;, topic=&#39;consumer_topic_dev&#39;, consumeMode=CONCURRENTLY, selectorType=TAG, selectorExpression=&#39;*&#39;, messageModel=CLUSTERING}</span></span></code></pre></div><h2 id="基本样例" tabindex="-1">基本样例 <a class="header-anchor" href="#基本样例" aria-label="Permalink to &quot;基本样例&quot;">​</a></h2><p>在基本样例中我们提供如下的功能场景：</p><ul><li>使用RocketMQ发送三种类型的消息：同步消息、异步消息和单向消息。其中前两种消息是可靠的，因为会有发送是否成功的应答。</li><li>使用RocketMQ来消费接收到的消息。</li></ul><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 生产和消费时最好加上Tag</span></span>
<span class="line"><span style="color:#A6ACCD;">rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">);</span></span></code></pre></div><p>其实这是 rocketmq 和 springboot 整合后设置 Tag 的方式（Tag：用于区分过滤同一主题下的不同业务类型的消息，非常实用） 在项目里往mq写入消息时，最好每条消息都带上tag，用于消费时根据业务过滤</p><h3 id="消息发送" tabindex="-1">消息发送 <a class="header-anchor" href="#消息发送" aria-label="Permalink to &quot;消息发送&quot;">​</a></h3><h4 id="发送同步消息" tabindex="-1">发送同步消息 <a class="header-anchor" href="#发送同步消息" aria-label="Permalink to &quot;发送同步消息&quot;">​</a></h4><p>这种可靠性同步地发送方式使用的比较广泛，比如：重要的消息通知，短信通知。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//发送普通同步消息-Object</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> payload</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通同步消息-Message</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送批量普通同步消息</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Collection</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通同步消息-Object，并设置发送超时时间</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> payload</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通同步消息-Message，并设置发送超时时间</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送批量普通同步消息，并设置发送超时时间</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Collection</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">T</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> messages</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通同步延迟消息，并设置超时，这个下文会演示</span></span>
<span class="line"><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> delayLevel</span><span style="color:#89DDFF;">)</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Resource</span></span>
<span class="line"><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RocketMQTemplate</span><span style="color:#A6ACCD;"> rocketMQTemplate</span><span style="color:#89DDFF;">;</span></span></code></pre></div><h5 id="方式一" tabindex="-1">方式一 <a class="header-anchor" href="#方式一" aria-label="Permalink to &quot;方式一&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Test</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">syncSendStr</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//syncSend和send是等价的</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> test1 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello world test1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">syncSend===&gt;{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">test1</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h5 id="方式二" tabindex="-1">方式二 <a class="header-anchor" href="#方式二" aria-label="Permalink to &quot;方式二&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Test</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">syncSendStr1</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//send底层还是会调用syncSend的代码</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//send无返回值</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">send</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello world </span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                          </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h5 id="方式三" tabindex="-1">方式三 <a class="header-anchor" href="#方式三" aria-label="Permalink to &quot;方式三&quot;">​</a></h5><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Test</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">syncSendPojo</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">User</span><span style="color:#89DDFF;">(</span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">张三</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> res </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">syncSend===&gt;{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205232318373.png" alt="image-20220523231812286" style="zoom:80%;"><p>这里存在两种消息体，一种是Object的，另一种是Message&lt;?&gt;的形式的，<code>其实我们发送Object的时候，底层是有帮我们做转换的，其实和我们在上层调用是一样的！</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//传入MessageBuilder</span></span>
<span class="line"><span style="color:#A6ACCD;">MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello world test1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//直接传入字符串、数字等</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//作用相同</span></span></code></pre></div><h4 id="发送异步消息" tabindex="-1">发送异步消息 <a class="header-anchor" href="#发送异步消息" aria-label="Permalink to &quot;发送异步消息&quot;">​</a></h4><p>异步消息通常用在对响应时间敏感的业务场景，即发送端不能容忍长时间地等待Broker的响应。</p><ul><li>指发送方发出数据后，不等接收方发回响应，接着发送下个数据包</li><li>关键实现异步发送回调接口（SendCallback）</li><li>在执行消息的异步发送时应用不需要等待服务器响应即可直接返回，通过回调接口接收务器响应，并对服务器的响 应结果进行处理</li><li>这种方式任然需要返回发送消息任务的执行结果，异步不影响后续任务，不会造成阻塞</li></ul><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//发送普通异步消息-Object</span></span>
<span class="line"><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> payload</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SendCallback</span><span style="color:#A6ACCD;"> sendCallback</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通异步消息-Message</span></span>
<span class="line"><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SendCallback</span><span style="color:#A6ACCD;"> sendCallback</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通异步消息-Object，并设置发送超时时间</span></span>
<span class="line"><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> payload</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SendCallback</span><span style="color:#A6ACCD;"> sendCallback</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通异步消息-Message，并设置发送超时时间</span></span>
<span class="line"><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SendCallback</span><span style="color:#A6ACCD;"> sendCallback</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">          </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//发送普通异步延迟消息，并设置超时，这个下文会演示</span></span>
<span class="line"><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> destination</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#89DDFF;font-style:italic;">?</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> message</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">SendCallback</span><span style="color:#A6ACCD;"> sendCallback</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">          </span><span style="color:#C792EA;">long</span><span style="color:#A6ACCD;"> timeout</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">          </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> delayLevel</span><span style="color:#89DDFF;">)</span></span></code></pre></div><p>示例</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/async</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello world test2 asyncSendStr</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                               </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SendCallback</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onSuccess</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">sendResult</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步消息发送成功:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onException</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">throwable</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步消息发送失败:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">throwable</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241042189.png" alt="image-20220524104252090" style="zoom:80%;"><p>接收消息(和正常接收一样)</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// tag分区,可以设置成tag1，tag2，不写默认接收所有tag</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">selectorExpression</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer1</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者1收到消息：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="单向发送消息-1" tabindex="-1">单向发送消息 <a class="header-anchor" href="#单向发送消息-1" aria-label="Permalink to &quot;单向发送消息&quot;">​</a></h4><p>这里普通单向消息就只有两个操作空间，这个不用多说了，一个是目标topic，另一个是Message</p><ul><li>这种方式主要用在不特别关心发送结果的场景，例如日志发送。</li><li>特点为只负责发送消息，不等待服务器回应且没有回调函数触发，即只发送请求不等待应答</li><li>此方式发送消息的过程耗时非常短，一般在微秒级别</li><li>应用场景：适用于某些耗时非常短，但对可靠性要求并不高的场景，例如日志收集</li></ul><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">//发送单向消息（只负责发送消息，不等待应答，不关心发送结果，如日志）</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">//响应信息在此控制台</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">oneWay</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test1</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendOneWay</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag3</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">发送单向消息</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241045261.png" alt="image-20220524104537213" style="zoom:80%;"><h3 id="消费消息" tabindex="-1">消费消息 <a class="header-anchor" href="#消费消息" aria-label="Permalink to &quot;消费消息&quot;">​</a></h3><p>topic需要和生产者的topic一致，consumerGroup属性是必须指定的，内容可以随意 selectorExpression的意思指的就是tag，默认为“*”，不设置的话会监听所有消息</p><p>注意：这个ConsumerSend2和上面ConsumerSend在<code>没有添加tag做区分时，不能共存，不然生产者发送一条消息，这两个都会去消费，如果类型不同会有一个报错，所以实际运用中最好加上tag</code>，写这只是让你看知道就行</p><h4 id="方式一-1" tabindex="-1">方式一 <a class="header-anchor" href="#方式一-1" aria-label="Permalink to &quot;方式一&quot;">​</a></h4><p>MessageExt：是一个消息接收通配符，不管发送的是String还是对象，都可接收，当然也可以像上面明确指定类型（我建议还是指定类型较方便）</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">common</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">message</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">LocalDateTime</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">format</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">DateTimeFormatter</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//s的body是字节数组，需要转换成字符串，才能正常输出</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">String</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息时间：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                 </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DateTimeFormatter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="方式二-1" tabindex="-1">方式二 <a class="header-anchor" href="#方式二-1" aria-label="Permalink to &quot;方式二&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">package</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">com</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">it</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">t1</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">LocalDateTime</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">format</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">DateTimeFormatter</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="方式三-1" tabindex="-1">方式三 <a class="header-anchor" href="#方式三-1" aria-label="Permalink to &quot;方式三&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// tag分区,可以设置成tag1，tag2，不写默认接收所有tag</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">selectorExpression</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">*</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer1</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者1收到消息：</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205231728136.png" alt="image-20220523172855026" style="zoom:80%;"><h2 id="顺序消息-1" tabindex="-1">顺序消息 <a class="header-anchor" href="#顺序消息-1" aria-label="Permalink to &quot;顺序消息&quot;">​</a></h2><p>消息有序指的是可以按照消息的发送顺序来消费(FIFO)。<code>RocketMQ可以严格的保证消息有序</code>，可以分为分区有序或者全局有序。</p><p>顺序消费的原理解析，在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列)；而消费消息的时候从多个queue上拉取消息，这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中，消费的时候只从这个queue上依次拉取，则就保证了顺序。当发送和消费参与的queue只有一个，则是全局有序；如果多个queue参与，则为分区有序，即相对每个queue，消息都是有序的。</p><p>下面用订单进行分区有序的示例。一个订单的顺序流程是：创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中，消费时，同一个OrderId获取到的肯定是同一个队列。</p><p>rocketmq默认发送的消息是进入多个消息队列，然后消费端多线程并发消费，所以默认情况，不是顺序消费消息的；</p><p><code>RocketMQTemplate</code>给我们提供了SendOrderly方法(有多个重载)，来实现发送顺序消息；包括以下：</p><p>syncSendOrderly，发送同步顺序消息；</p><p>asyncSendOrderly，发送异步顺序消息；</p><p>sendOneWayOrderly，发送单向顺序消息</p><p><code>注意：主要通过第三个参数hashKey来区分不同的queue</code></p><h3 id="同步顺序消息" tabindex="-1">同步顺序消息 <a class="header-anchor" href="#同步顺序消息" aria-label="Permalink to &quot;同步顺序消息&quot;">​</a></h3><p>生产消息</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/shunxu</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test3</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// hashKey用来计算决定消息发送到哪个消息队列 一般是订单ID，产品ID等</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 用户一</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,创建</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,支付</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,完成</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">// 用户二</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232,创建</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232,支付</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232,完成</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                     </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456232</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>消费消息</p><p>注意：<code>要指定顺序消费</code></p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">common</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">message</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ConsumeMode</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">LocalDateTime</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">format</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">DateTimeFormatter</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定消费模式为顺序消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumeMode</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ConsumeMode</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">ORDERLY</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//s的body是字节数组，需要转换成字符串，才能正常输出</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">String</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息时间：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                 </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DateTimeFormatter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241304405.png" alt="image-20220524130441352" style="zoom:80%;"><h3 id="异步顺序消息" tabindex="-1">异步顺序消息 <a class="header-anchor" href="#异步顺序消息" aria-label="Permalink to &quot;异步顺序消息&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">PostMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/shunxu2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test4</span><span style="color:#89DDFF;">(@</span><span style="color:#C792EA;">RequestBody</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">User</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">asyncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag6</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> user</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                                      </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SendCallback</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onSuccess</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">sendResult</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步顺序消息发送成功:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onException</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">throwable</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步顺序发送失败:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">throwable</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">});</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241315970.png" alt="image-20220524131525907" style="zoom:80%;"><h3 id="单向顺序消息" tabindex="-1">单向顺序消息 <a class="header-anchor" href="#单向顺序消息" aria-label="Permalink to &quot;单向顺序消息&quot;">​</a></h3><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/shunxu2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test4</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendOneWayOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,创建</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,支付</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSendOrderly</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231,完成</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">98456231</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241318093.png" alt="image-20220524131809047" style="zoom:80%;"><h2 id="延时消息-1" tabindex="-1">延时消息 <a class="header-anchor" href="#延时消息-1" aria-label="Permalink to &quot;延时消息&quot;">​</a></h2><h3 id="同步延迟消息" tabindex="-1">同步延迟消息 <a class="header-anchor" href="#同步延迟消息" aria-label="Permalink to &quot;同步延迟消息&quot;">​</a></h3><p>RocketMQ延迟队列的核心思路是：所有的延迟消息由producer发出之后，都会存放到同一个topic（SCHEDULE_TOPIC_XXXX）下，不同的延迟级别会对应不同的队列序号，当延迟时间到之后，由定时线程读取转换为普通的消息存的真实指定的topic下，此时对于consumer端此消息才可见，从而被consumer费。</p><blockquote><p><strong>注意：</strong> RocketMQ不支持任意时间的延时，只支持以下几个固定的延时等级</p></blockquote><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">private</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> messageDelayLevel </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">;</span></span></code></pre></div><p>下面我们结合SprintBoot利用RocketMQ发送延时消息</p><p>编写生产者</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">/**</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 同步延迟消息</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * rocketMQ的延迟消息发送其实是已发送就已经到broker端了，然后消费端会延迟收到消息。</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * RocketMQ 目前只支持固定精度的定时消息。</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 固定等级：1到18分别对应1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> * 延迟的底层方法是用定时任务实现的。</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;"> */</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/yanshi</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test3</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#C792EA;">MessageBuilder</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hello world</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#89DDFF;">                    </span><span style="color:#676E95;font-style:italic;">//2000是超时时间，4表示等级4，对应30s后响应</span></span>
<span class="line"><span style="color:#A6ACCD;">                    </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2000</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">4</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">发送消息时间： {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DateTimeFormatter</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy年MM月dd日 HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>编写消费者</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//s的body是字节数组，需要转换成字符串，才能正常输出</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">String</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息时间：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                 </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DateTimeFormatter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241110762.png" alt="image-20220524111037719" style="zoom:80%;"><h3 id="异步延时消息" tabindex="-1">异步延时消息 <a class="header-anchor" href="#异步延时消息" aria-label="Permalink to &quot;异步延时消息&quot;">​</a></h3><p>在原来异步的基础上加上最后一个参数3</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/yanshi</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test3</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">asyncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:t5</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">            MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步消息...</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">SendCallback</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onSuccess</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">sendResult</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步消息发送成功:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">sendResult</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">                    log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">发送消息时间： {}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DateTimeFormatter</span></span>
<span class="line"><span style="color:#A6ACCD;">                            </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy年MM月dd日 HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">                            </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()));</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onException</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Throwable</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">throwable</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">                   log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">异步消息发送失败:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">throwable</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getMessage</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2000</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241222482.png" alt="image-20220524122211434" style="zoom:80%;"><h3 id="修改延时级别" tabindex="-1">修改延时级别 <a class="header-anchor" href="#修改延时级别" aria-label="Permalink to &quot;修改延时级别&quot;">​</a></h3><p>RocketMQ的延迟等级可以进行修改，以满足自己的业务需求，可以修改/添加新的level。例如：你想支持1天的延迟，修改最后一个level的值为1d，这个时候依然是18个level；也可以增加一个1d，这个时候总共就有19个level。打开RocketMQ的配置文件，修改<code>messageDelayLevel</code> 属性</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">brokerClusterName </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> DefaultCluster</span></span>
<span class="line"><span style="color:#A6ACCD;">brokerName </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> broker</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">a</span></span>
<span class="line"><span style="color:#A6ACCD;">brokerId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span></span>
<span class="line"><span style="color:#A6ACCD;">deleteWhen </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">04</span></span>
<span class="line"><span style="color:#A6ACCD;">fileReservedTime </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">48</span></span>
<span class="line"><span style="color:#A6ACCD;">brokerRole </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ASYNC_MASTER</span></span>
<span class="line"><span style="color:#A6ACCD;">flushDiskType </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ASYNC_FLUSH</span></span>
<span class="line"><span style="color:#A6ACCD;">storePathRootDir </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">app</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">rocketmq</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">data</span></span>
<span class="line"><span style="color:#A6ACCD;">messageDelayLevel</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">90s</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5s</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10s</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">30s</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">4m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">6m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">7m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">8m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">9m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">20m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">30m</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1h</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2h</span></span></code></pre></div><p>这次将延时等级1修改成了90s，生产者发送消息后需要90s后再进行消息投递。修改完成后重启RocketMQ。</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">nohup sh mqbroker </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">n localhost:</span><span style="color:#F78C6C;">9876</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">c ..</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">conf</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">broker.conf </span><span style="color:#89DDFF;">&amp;</span></span></code></pre></div><p>通过比对发送时间与消费时间证明延时等级修改生效。</p><h2 id="批量消息-1" tabindex="-1">批量消息 <a class="header-anchor" href="#批量消息-1" aria-label="Permalink to &quot;批量消息&quot;">​</a></h2><p>批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic，相同的waitStoreMsgOK，而且不能是延时消息。此外，这一批消息的总大小不应超过4MB。</p><h3 id="发送批量消息" tabindex="-1">发送批量消息 <a class="header-anchor" href="#发送批量消息" aria-label="Permalink to &quot;发送批量消息&quot;">​</a></h3><p>如果您每次只发送不超过4MB的消息，则很容易使用批处理，样例如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/piliang1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test3</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">messaging</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">&gt;&gt;</span><span style="color:#A6ACCD;"> msgs </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">ArrayList</span><span style="color:#89DDFF;">&lt;&gt;();</span></span>
<span class="line"><span style="color:#A6ACCD;">    msgs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hello world 0</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    msgs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hello world 1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    msgs</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">add</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">Hello world 2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">());</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">SendResult</span><span style="color:#A6ACCD;"> sr </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">syncSend</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msgs</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">60000</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    System</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">out</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">println</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">--- Batch messages send result :</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> sr</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241103187.png" alt="image-20220524110336141" style="zoom:80%;"><h2 id="过滤消息" tabindex="-1">过滤消息 <a class="header-anchor" href="#过滤消息" aria-label="Permalink to &quot;过滤消息&quot;">​</a></h2><p>提供消息过滤的功能，用于同一topic下，区分不同业务场景的消息。</p><p>Tag，即消息标签，用于对某个Topic下的消息进行分类。消息队列RocketMQ版的生产者在发送消息时，已经指定消息的Tag，消费者需根据已经指定的Tag来进行订阅。</p><p>使用springboot-starter的方式发送tag消息，只需要如下形式即可，不需要单独指定参数：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">topic</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;">tag</span></span></code></pre></div><h3 id="根据tag过滤消息" tabindex="-1">根据TAG过滤消息 <a class="header-anchor" href="#根据tag过滤消息" aria-label="Permalink to &quot;根据TAG过滤消息&quot;">​</a></h3><p>消息发送端只能设置一个tag，消息接收端可以设置多个tag。</p><p><code>消息接收端</code></p><p>接收消息端通过 ‘||’ 设置多个tag，如下：</p><div class="language-erlang"><button title="Copy Code" class="copy"></button><span class="lang">erlang</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">tag1</span><span style="color:#A6ACCD;"> || </span><span style="color:#89DDFF;">tag2</span><span style="color:#A6ACCD;"> || </span><span style="color:#89DDFF;">tag3</span><span style="color:#A6ACCD;"> || ...</span></span></code></pre></div><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 三个标签任何一个都能被消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">selectorExpression</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">tag1 || tag2 || tag3</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定消费模式为顺序消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumeMode</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ConsumeMode</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">ORDERLY</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span></code></pre></div><ul><li>selectorType：指明了消息选择通过tag的方式，默认值SelectorType.TAG。</li><li>messageModel：指明了消息消费模式，默认值MessageModel.CLUSTERING每条消息只能有一个消费端进行消费；MessageModel.BROADCASTING广播消息，所有订阅者都能收到消息。</li><li>selectorExpression：指明了能够接收哪些tag，多个tag通过 ‘||’ 或方式。</li></ul><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">oneWay</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test1</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    rocketMQTemplate</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendOneWay</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str:tag3</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">发送单向消息</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>发送消息时设置了tag为：tag3；那接收消息端只有设置了selectorExpression值中包含了tag12的才能接收消息。</p><h3 id="根据sql表达式过滤消息" tabindex="-1">根据SQL表达式过滤消息 <a class="header-anchor" href="#根据sql表达式过滤消息" aria-label="Permalink to &quot;根据SQL表达式过滤消息&quot;">​</a></h3><p>默认不支持SQL表达式，启动报错：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">The broker does not support consumer to filter message by SQL92</span></span></code></pre></div><p>找到<code>broker.conf</code>配置文件,加下：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">enablePropertyFilter</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">true</span></span></code></pre></div><p>SQL表达式方式可以根据发送消息时输入的属性进行一些计算。在RocketMQ定义的语法下，可以实现一些有趣的逻辑。如下：</p><p>消费者将接收包含tag1或tag2或tag3的消息。但是限制是一个消息只能有一个标签，这对于复杂的场景可能不起作用。在这种情况下，可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下，可以实现一些简单的逻辑。下面是一个例子：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">------------</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> message  </span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">|----------|</span><span style="color:#A6ACCD;">  a </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5</span><span style="color:#A6ACCD;"> AND b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">abc</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">--------------------&gt;</span><span style="color:#A6ACCD;"> Gotten</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">abc</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> c </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> true </span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">------------</span></span>
<span class="line"><span style="color:#89DDFF;">------------</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> message  </span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">|----------|</span><span style="color:#A6ACCD;">   a </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">5</span><span style="color:#A6ACCD;"> AND b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">abc</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">--------------------&gt;</span><span style="color:#A6ACCD;"> Missed</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">abc</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">|</span><span style="color:#A6ACCD;"> c </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> true </span><span style="color:#89DDFF;">|</span></span>
<span class="line"><span style="color:#89DDFF;">------------</span></span></code></pre></div><h4 id="基本语法" tabindex="-1">基本语法 <a class="header-anchor" href="#基本语法" aria-label="Permalink to &quot;基本语法&quot;">​</a></h4><p>RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。</p><ul><li>数值比较，比如：<strong>&gt;，&gt;=，&lt;，&lt;=，BETWEEN，=；</strong></li><li>字符比较，比如：<strong>=，&lt;&gt;，IN；</strong></li><li><strong>IS NULL</strong> 或者 <strong>IS NOT NULL；</strong></li><li>逻辑符号 <strong>AND，OR，NOT；</strong></li></ul><p>常量支持类型为：</p><ul><li>数值，比如：<strong>123，3.1415；</strong></li><li>字符，比如：<strong>&#39;abc&#39;，必须用单引号包裹起来；</strong></li><li><strong>NULL</strong>，特殊的常量</li><li>布尔值，<strong>TRUE</strong> 或 <strong>FALSE</strong></li></ul><p>只有使用push模式的消费者才能用使用SQL92标准的sql语句，接口如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">subscribe</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">finalString topic</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">final</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">MessageSelector</span><span style="color:#A6ACCD;"> messageSelector</span><span style="color:#89DDFF;">)</span></span></code></pre></div><h3 id="消费者样例" tabindex="-1">消费者样例 <a class="header-anchor" href="#消费者样例" aria-label="Permalink to &quot;消费者样例&quot;">​</a></h3><p>用MessageSelector.bySql来使用sql筛选消息</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">first-topic-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//tag0-tag4都可以消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">selectorExpression</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">tag between 0 and 4</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">selectorType</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> SelectorType</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SQL92</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//s的body是字节数组，需要转换成字符串，才能正常输出</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">String</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息时间：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                 </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DateTimeFormatter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h2 id="消息事务" tabindex="-1">消息事务 <a class="header-anchor" href="#消息事务" aria-label="Permalink to &quot;消息事务&quot;">​</a></h2><p>RocketMQ还有一个很重要的特性：事务，其它mq可是不支持的，利用事务可以做很多事，如跟钱相关的业务、分布式事务，不过事务的实现过程要麻烦点</p><p>事务消息共有三种状态，<code>提交状态、回滚状态、中间状态</code>：</p><ul><li>TransactionStatus.CommitTransaction: 提交事务，它允许消费者消费此消息。</li><li>TransactionStatus.RollbackTransaction: 回滚事务，它代表该消息将被删除，不允许被消费。</li><li>TransactionStatus.Unknown: 中间状态，它代表需要检查消息队列来确定状态。</li></ul><h3 id="场景模拟" tabindex="-1">场景模拟 <a class="header-anchor" href="#场景模拟" aria-label="Permalink to &quot;场景模拟&quot;">​</a></h3><p>场景：假设我们现在有这样的业务：用户充值网费会获得积分，且1元=1积分，用户服务中充值100元，积分服务中要对该用户增加100积分</p><p>分析：像这种跨服务、跨库的操作，我们要保证这两个操作要么一起成功、要么一起失败，采用RocketMQ的方案就是：<code>RocketMQ事务消息+本地事务+监听消费，来达到最终一致性</code></p><p>在实现之前，先得介绍一下RocketMQ的事务</p><h3 id="rocketmq事务介绍" tabindex="-1">RocketMQ事务介绍 <a class="header-anchor" href="#rocketmq事务介绍" aria-label="Permalink to &quot;RocketMQ事务介绍&quot;">​</a></h3><ol><li><p>基本概念 （1）Half Message：也叫 Prepare Message，翻译为 “半消息”或“准备消息”，指的是暂时无法投递的消息，即消息成功发送到MQ服务器，暂时还不能给消费者进行消费，只有当服务器接收到生产者传来的二次确认时，才能被消费者消费 （2）Message Status Check：消息状态回查。网络断开连接或生产者应用程序重新启动可能会丢失对事务性消息的第二次确认，当MQ服务器发现某条消息长时间保持半消息状态时，它会向消息生产者发送一个请求，去检查消息的最终状态（“提交”或“回滚”）</p></li><li><p>执行流程图</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241350455.png" alt="image-20220524135005394" style="zoom:80%;"></li><li><p>生产者发送半消息到 MQ Server，暂时不能投递，不会被消费</p></li><li><p>半消息发送成功后，生产者这边执行本地事务</p></li><li><p>生产者根据本地事务执行结果，向 MQ Server 发送 commit 或 rollback 消息进行二次确认</p></li><li><p>如果 MQ Server 接收到的 commit，则将半消息标记为可投递状态，此时消费者就能进行消费；如果收到的是 rollback，则将半消息直接丢弃，不会进行消费</p></li><li><p>如果 MQ Server 未收到二次确认消息，MQ Server 则会定时（默认1分钟）向生产者发送回查消息，检查本地事务状态，然后生产者根据本地事务回查结果再次向 MQ Server 发送 commit 或 rollback消息</p></li></ol><h3 id="发送事务消息" tabindex="-1">发送事务消息 <a class="header-anchor" href="#发送事务消息" aria-label="Permalink to &quot;发送事务消息&quot;">​</a></h3><h4 id="创建事务性生产者" tabindex="-1">创建事务性生产者 <a class="header-anchor" href="#创建事务性生产者" aria-label="Permalink to &quot;创建事务性生产者&quot;">​</a></h4><p>使用 <code>TransactionMQProducer</code>类创建生产者，并指定唯一的 <code>ProducerGroup</code>，就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态在请参考前一节。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">GetMapping</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/shiwu2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test4</span><span style="color:#89DDFF;">(){</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">String</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> tags </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">};</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">for</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">++)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">TransactionSendResult</span><span style="color:#A6ACCD;"> res</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">rocketMQTemplate</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sendMessageInTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">transaction-str:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">tags</span><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">],</span></span>
<span class="line"><span style="color:#A6ACCD;">             MessageBuilder</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">withPayload</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">事务消息===&gt;</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">).</span><span style="color:#82AAFF;">build</span><span style="color:#89DDFF;">(),</span><span style="color:#A6ACCD;">i</span><span style="color:#89DDFF;">+</span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getLocalTransactionState</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">LocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">COMMIT_MESSAGE</span><span style="color:#89DDFF;">)&amp;&amp;</span></span>
<span class="line"><span style="color:#A6ACCD;">           res</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getSendStatus</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">SendStatus</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">SEND_OK</span><span style="color:#89DDFF;">)){</span></span>
<span class="line"><span style="color:#A6ACCD;">            log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">事物消息发送成功</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">事物消息发送结果:{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="实现事务的监听接口" tabindex="-1">实现事务的监听接口 <a class="header-anchor" href="#实现事务的监听接口" aria-label="Permalink to &quot;实现事务的监听接口&quot;">​</a></h4><p>当发送半消息成功时，我们使用 <code>executeLocalTransaction</code> 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。<code>checkLocalTransaction</code> 方法用于检查本地事务状态，并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">commons</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">lang3</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">StringUtils</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQTransactionListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQLocalTransactionListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQLocalTransactionState</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">messaging</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Message</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQTransactionListener</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">TransactionListenerImpl</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQLocalTransactionListener</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RocketMQLocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">executeLocalTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msg</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">arg</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 执行本地事务</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> tag </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> String</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">valueOf</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getHeaders</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">get</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">rocketmq_TAGS</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">));</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGA</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> tag</span><span style="color:#89DDFF;">)){</span></span>
<span class="line"><span style="color:#89DDFF;">            </span><span style="color:#676E95;font-style:italic;">//这里只讲TAGA消息提交，状态为可执行</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> RocketMQLocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">COMMIT</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> tag</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> RocketMQLocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">ROLLBACK</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">else</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">equals</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">TAGC</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">tag</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> RocketMQLocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">UNKNOWN</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> RocketMQLocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">UNKNOWN</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">    </span><span style="color:#676E95;font-style:italic;">//mq回调检查本地事务执行情况</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">RocketMQLocalTransactionState</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">checkLocalTransaction</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Message</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">msg</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">checkLocalTransaction===&gt;{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;">msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> RocketMQLocalTransactionState</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">COMMIT</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h4 id="消费消息-1" tabindex="-1">消费消息 <a class="header-anchor" href="#消费消息-1" aria-label="Permalink to &quot;消费消息&quot;">​</a></h4><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">lombok</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">extern</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">slf4j</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Slf4j</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">common</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">message</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">ConsumeMode</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">annotation</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">apache</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">rocketmq</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">spring</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">core</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">RocketMQListener</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">org</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">springframework</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">stereotype</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">Component</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">LocalDateTime</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F78C6C;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">java</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">format</span><span style="color:#89DDFF;">.</span><span style="color:#C792EA;">DateTimeFormatter</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Slf4j</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Component</span></span>
<span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">RocketMQMessageListener</span><span style="color:#89DDFF;">(</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">topic</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">transaction-str</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumerGroup</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">myGroup</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">// 指定消费模式为顺序消费</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#FFCB6B;">consumeMode</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> ConsumeMode</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">ORDERLY</span></span>
<span class="line"><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">Consumer</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">RocketMQListener</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">MessageExt</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">void</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">onMessage</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">MessageExt</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">s</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">        </span><span style="color:#676E95;font-style:italic;">//s的body是字节数组，需要转换成字符串，才能正常输出</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">byte</span><span style="color:#89DDFF;">[]</span><span style="color:#A6ACCD;"> body </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> s</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getBody</span><span style="color:#89DDFF;">();</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#C792EA;">String</span><span style="color:#A6ACCD;"> msg </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">new</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">String</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">body</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> msg</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">info</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">消费者收到消息时间：{}</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">                 LocalDateTime</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#89DDFF;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">                 </span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">format</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">DateTimeFormatter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">ofPattern</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">yyyy-MM-dd HH:mm:ss</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)));</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205241336418.png" alt="image-20220524133647349" style="zoom:80%;"><h3 id="事务消息使用上的限制" tabindex="-1">事务消息使用上的限制 <a class="header-anchor" href="#事务消息使用上的限制" aria-label="Permalink to &quot;事务消息使用上的限制&quot;">​</a></h3><ol><li>事务消息不支持延时消息和批量消息。</li><li>为了避免单个消息被检查太多次而导致半队列消息累积，我们默认将单个消息的检查次数限制为 15 次，但是用户可以通过 Broker 配置文件的 <code>transactionCheckMax</code>参数来修改此限制。如果已经检查某条消息超过 N 次的话（ N = <code>transactionCheckMax</code> ） 则 Broker 将丢弃此消息，并在默认情况下同时打印错误日志。用户可以通过重写 <code>AbstractTransactionalMessageCheckListener</code> 类来修改这个行为。</li><li>事务消息将在 Broker 配置文件中的参数 transactionTimeout 这样的特定时间长度之后被检查。当发送事务消息时，用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制，该参数优先于 <code>transactionTimeout</code> 参数。</li><li>事务性消息可能不止一次被检查或消费。</li><li>提交给用户的目标主题消息可能会失败，目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证，如果希望确保事务消息不丢失、并且事务完整性得到保证，建议使用同步的双重写入机制。</li><li>事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同，事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。</li></ol><h2 id="rocketmq二次封装" tabindex="-1">RocketMQ二次封装 <a class="header-anchor" href="#rocketmq二次封装" aria-label="Permalink to &quot;RocketMQ二次封装&quot;">​</a></h2><p>在解释为什么要二次封装之前先来看看RocketMQ官方文档中推荐的最佳实践</p><ol><li>消息发送成功或者失败要打印消息日志，用于业务排查问题。</li><li>如果消息量较少，建议在消费入口方法打印消息，消费耗时等，方便后续排查问题。</li><li>RocketMQ 无法避免消息重复（Exactly-Once），所以如果业务对消费重复非常敏感，务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键，可以是msgId，也可以是消息内容中的唯一标识字段，例如订单Id等。</li></ol><p><strong>上面三个步骤基本每次发送消息或者消费消息都要实现，属于重复动作。</strong></p><p>接下来讨论的是<strong>在RocketMQ中发送消息时选择何种消息类型最为合适。</strong></p><p>在RocketMQ中有四种可选格式：</p><ol><li>发送Json对象</li><li>发送转Json后的String对象</li><li>根据业务封装对应实体类</li><li>直接使用原生MessageExt接收。</li></ol><p>对于如何选择消息类型，需要考虑到<strong>消费者在不查看消息发送者的情况下，如何获取消息的含义</strong>。因此，在这种情况下，使用第三种方式即根据业务封装对应实体类的方式最为合适，也是大多数开发者在发送消息时的常用方式。</p><p>有了上面两点结论以后我们来看看为什么要对RocketMQ二次封装。</p><h3 id="为什么要二次封装" tabindex="-1">为什么要二次封装 <a class="header-anchor" href="#为什么要二次封装" aria-label="Permalink to &quot;为什么要二次封装&quot;">​</a></h3><p>按照上述最佳实践，一个完整的消息传递链路从生产到消费应包括 <strong>准备消息、发送消息、记录消息日志、处理发送失败、记录接收消息日志、处理业务逻辑、异常处理和异常重试</strong> 等步骤。</p><p>虽然使用原生RocketMQ可以完成这些动作，但每个生产者和消费者都需要编写大量重复的代码来完成相同的任务，这就是需要进行二次封装的原因。我们希望通过二次封装，<strong>生产者只需准备好消息实体并调用封装后的工具类发送，而消费者只需处理核心业务逻辑，其他公共逻辑会得到统一处理。</strong></p><p>在二次封装中，关键是找出框架在日常使用中所涵盖的许多操作，以及区分哪些操作是可变的，哪些是不变的。以上述例子为例，实际上只有生产者的消息准备和消费者的业务处理是可变的操作，需要根据需求进行处理，而其他步骤可以固定下来形成一个模板。</p><p>当然，本文提到的二次封装不是指对源代码进行封装，而是针对工具的原始使用方式进行的封装。可以将其与Mybatis和Mybatis-plus区分开来。这两者都能完成任务，只不过Mybatis-plus更为简单便捷。</p><h3 id="实现二次封装" tabindex="-1">实现二次封装 <a class="header-anchor" href="#实现二次封装" aria-label="Permalink to &quot;实现二次封装&quot;">​</a></h3><p>实现二次封装需要创建一个自定义的starter，这样其他项目只需要依赖此starter即可使用封装功能。同时，在自定义starter中还需要解决文章第二部分中提到的一些问题。</p><p>代码结构如下所示：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PxMzT0Oibf4hOW85NuRu9P9pIibJib7ibEZZoJlZlUicqkbTtDrZYl8vEdytOfmnAuic51bg76uou2zicQO7Zc1E5Nhibw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h4 id="消息实体类的封装" tabindex="-1">消息实体类的封装 <a class="header-anchor" href="#消息实体类的封装" aria-label="Permalink to &quot;消息实体类的封装&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">/**</span></span>
<span class="line"><span style="color:#A6ACCD;"> * 消息实体，所有消息都需要继承此类</span></span>
<span class="line"><span style="color:#A6ACCD;"> */</span></span>
<span class="line"><span style="color:#A6ACCD;">@Data</span></span>
<span class="line"><span style="color:#A6ACCD;">public abstract class BaseMessage {</span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 业务键，用于RocketMQ控制台查看消费情况</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected String key;</span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 发送消息来源，用于排查问题</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected String source = &quot;&quot;;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 发送时间</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected LocalDateTime sendTime = LocalDateTime.now();</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 重试次数，用于判断重试次数，超过重试次数发送异常警告</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected Integer retryTimes = 0;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>后面所有发送的消息实体都需要继承此实体类。</p><h4 id="消息发送工具类的封装" tabindex="-1">消息发送工具类的封装 <a class="header-anchor" href="#消息发送工具类的封装" aria-label="Permalink to &quot;消息发送工具类的封装&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Slf4j</span></span>
<span class="line"><span style="color:#A6ACCD;">@RequiredArgsConstructor(onConstructor = @__(@Autowired))</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketMQEnhanceTemplate {</span></span>
<span class="line"><span style="color:#A6ACCD;">    private final RocketMQTemplate template;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    private RocketEnhanceProperties rocketEnhanceProperties;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public RocketMQTemplate getTemplate() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return template;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 根据系统上下文自动构建隔离后的topic</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 构建目的地</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    public String buildDestination(String topic, String tag) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        topic = reBuildTopic(topic);</span></span>
<span class="line"><span style="color:#A6ACCD;">        return topic + &quot;:&quot; + tag;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 根据环境重新隔离topic</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @param topic 原始topic</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    private String reBuildTopic(String topic) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        if(rocketEnhanceProperties.isEnabledIsolation() &amp;&amp; StringUtils.hasText(rocketEnhanceProperties.getEnvironment())){</span></span>
<span class="line"><span style="color:#A6ACCD;">            return topic +&quot;_&quot; + rocketEnhanceProperties.getEnvironment();</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        return topic;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 发送同步消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    public &lt;T extends BaseMessage&gt; SendResult send(String topic, String tag, T message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 注意分隔符</span></span>
<span class="line"><span style="color:#A6ACCD;">        return send(buildDestination(topic,tag), message);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public &lt;T extends BaseMessage&gt; SendResult send(String destination, T message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 设置业务键，此处根据公共的参数进行处理</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 更多的其它基础业务处理...</span></span>
<span class="line"><span style="color:#A6ACCD;">        Message&lt;T&gt; sendMessage = MessageBuilder.withPayload(message).setHeader(RocketMQHeaders.KEYS, message.getKey()).build();</span></span>
<span class="line"><span style="color:#A6ACCD;">        SendResult sendResult = template.syncSend(destination, sendMessage);</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 此处为了方便查看给日志转了json，根据选择选择日志记录方式，例如ELK采集</span></span>
<span class="line"><span style="color:#A6ACCD;">        log.info(&quot;[{}]同步消息[{}]发送结果[{}]&quot;, destination, JSONObject.toJSON(message), JSONObject.toJSON(sendResult));</span></span>
<span class="line"><span style="color:#A6ACCD;">        return sendResult;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 发送延迟消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    public &lt;T extends BaseMessage&gt; SendResult send(String topic, String tag, T message, int delayLevel) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return send(buildDestination(topic,tag), message, delayLevel);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public &lt;T extends BaseMessage&gt; SendResult send(String destination, T message, int delayLevel) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        Message&lt;T&gt; sendMessage = MessageBuilder.withPayload(message).setHeader(RocketMQHeaders.KEYS, message.getKey()).build();</span></span>
<span class="line"><span style="color:#A6ACCD;">        SendResult sendResult = template.syncSend(destination, sendMessage, 3000, delayLevel);</span></span>
<span class="line"><span style="color:#A6ACCD;">        log.info(&quot;[{}]延迟等级[{}]消息[{}]发送结果[{}]&quot;, destination, delayLevel, JSONObject.toJSON(message), JSONObject.toJSON(sendResult));</span></span>
<span class="line"><span style="color:#A6ACCD;">        return sendResult;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>这里封装了一个消息发送类，实现了日志记录以及自动重建topic的功能（即生产者实现环境隔离），后面项目中只需要注入RocketMQEnhanceTemplate来实现消息的发送。</p><h4 id="消费者的封装" tabindex="-1">消费者的封装 <a class="header-anchor" href="#消费者的封装" aria-label="Permalink to &quot;消费者的封装&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Slf4j</span></span>
<span class="line"><span style="color:#A6ACCD;">public abstract class EnhanceMessageHandler&lt;T extends BaseMessage&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 默认重试次数</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    private static final int MAX_RETRY_TIMES = 3;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 延时等级</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    private static final int DELAY_LEVEL = EnhanceMessageConstant.FIVE_SECOND;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Resource</span></span>
<span class="line"><span style="color:#A6ACCD;">    private RocketMQEnhanceTemplate rocketMQEnhanceTemplate;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 消息处理</span></span>
<span class="line"><span style="color:#A6ACCD;">     *</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @param message 待处理消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @throws Exception 消费异常</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected abstract void handleMessage(T message) throws Exception;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 超过重试次数消息，需要启用isRetry</span></span>
<span class="line"><span style="color:#A6ACCD;">     *</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @param message 待处理消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected abstract void handleMaxRetriesExceeded(T message);</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 是否需要根据业务规则过滤消息，去重逻辑可以在此处处理</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @param message 待处理消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @return true: 本次消息被过滤，false：不过滤</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected boolean filter(T message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return false;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 是否异常时重复发送</span></span>
<span class="line"><span style="color:#A6ACCD;">     *</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @return true: 消息重试，false：不重试</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected abstract boolean isRetry();</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 消费异常时是否抛出异常</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 返回true，则由rocketmq机制自动重试</span></span>
<span class="line"><span style="color:#A6ACCD;">     * false：消费异常(如果没有开启重试则消息会被自动ack)</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected abstract boolean throwException();</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 最大重试次数</span></span>
<span class="line"><span style="color:#A6ACCD;">     *</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @return 最大重试次数，默认5次</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected int getMaxRetryTimes() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return MAX_RETRY_TIMES;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * isRetry开启时，重新入队延迟时间</span></span>
<span class="line"><span style="color:#A6ACCD;">     * @return -1：立即入队重试</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected int getDelayLevel() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return DELAY_LEVEL;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 使用模板模式构建消息消费框架，可自由扩展或删减</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void dispatchMessage(T message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 基础日志记录被父类处理了</span></span>
<span class="line"><span style="color:#A6ACCD;">        log.info(&quot;消费者收到消息[{}]&quot;, JSONObject.toJSON(message));</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">        if (filter(message)) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            log.info(&quot;消息id{}不满足消费条件，已过滤。&quot;,message.getKey());</span></span>
<span class="line"><span style="color:#A6ACCD;">            return;</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 超过最大重试次数时调用子类方法处理</span></span>
<span class="line"><span style="color:#A6ACCD;">        if (message.getRetryTimes() &gt; getMaxRetryTimes()) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            handleMaxRetriesExceeded(message);</span></span>
<span class="line"><span style="color:#A6ACCD;">            return;</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        try {</span></span>
<span class="line"><span style="color:#A6ACCD;">            long now = System.currentTimeMillis();</span></span>
<span class="line"><span style="color:#A6ACCD;">            handleMessage(message);</span></span>
<span class="line"><span style="color:#A6ACCD;">            long costTime = System.currentTimeMillis() - now;</span></span>
<span class="line"><span style="color:#A6ACCD;">            log.info(&quot;消息{}消费成功，耗时[{}ms]&quot;, message.getKey(),costTime);</span></span>
<span class="line"><span style="color:#A6ACCD;">        } catch (Exception e) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            log.error(&quot;消息{}消费异常&quot;, message.getKey(),e);</span></span>
<span class="line"><span style="color:#A6ACCD;">            // 是捕获异常还是抛出，由子类决定</span></span>
<span class="line"><span style="color:#A6ACCD;">            if (throwException()) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                //抛出异常，由DefaultMessageListenerConcurrently类处理</span></span>
<span class="line"><span style="color:#A6ACCD;">                throw new RuntimeException(e);</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">            //此时如果不开启重试机制，则默认ACK了</span></span>
<span class="line"><span style="color:#A6ACCD;">            if (isRetry()) {</span></span>
<span class="line"><span style="color:#A6ACCD;">                handleRetry(message);</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    protected void handleRetry(T message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 获取子类RocketMQMessageListener注解拿到topic和tag</span></span>
<span class="line"><span style="color:#A6ACCD;">        RocketMQMessageListener annotation = this.getClass().getAnnotation(RocketMQMessageListener.class);</span></span>
<span class="line"><span style="color:#A6ACCD;">        if (annotation == null) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            return;</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        //重新构建消息体</span></span>
<span class="line"><span style="color:#A6ACCD;">        String messageSource = message.getSource();</span></span>
<span class="line"><span style="color:#A6ACCD;">        if(!messageSource.startsWith(EnhanceMessageConstant.RETRY_PREFIX)){</span></span>
<span class="line"><span style="color:#A6ACCD;">            message.setSource(EnhanceMessageConstant.RETRY_PREFIX + messageSource);</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        message.setRetryTimes(message.getRetryTimes() + 1);</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">        SendResult sendResult;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">        try {</span></span>
<span class="line"><span style="color:#A6ACCD;">            // 如果消息发送不成功，则再次重新发送，如果发送异常则抛出由MQ再次处理(异常时不走延迟消息)</span></span>
<span class="line"><span style="color:#A6ACCD;">            sendResult = rocketMQEnhanceTemplate.send(annotation.topic(), annotation.selectorExpression(), message, getDelayLevel());</span></span>
<span class="line"><span style="color:#A6ACCD;">        } catch (Exception ex) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            // 此处捕获之后，相当于此条消息被消息完成然后重新发送新的消息</span></span>
<span class="line"><span style="color:#A6ACCD;">            //由生产者直接发送</span></span>
<span class="line"><span style="color:#A6ACCD;">            throw new RuntimeException(ex);</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 发送失败的处理就是不进行ACK，由RocketMQ重试</span></span>
<span class="line"><span style="color:#A6ACCD;">        if (sendResult.getSendStatus() != SendStatus.SEND_OK) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            throw new RuntimeException(&quot;重试消息发送失败&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>使用模版设计模式定义了消息消费的骨架，实现了日志打印，异常处理，异常重试等公共逻辑，消息过滤（查重）、业务处理则交由子类实现。</p><h4 id="基础配置类" tabindex="-1">基础配置类 <a class="header-anchor" href="#基础配置类" aria-label="Permalink to &quot;基础配置类&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Configuration</span></span>
<span class="line"><span style="color:#A6ACCD;">@EnableConfigurationProperties(RocketEnhanceProperties.class)</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketMQEnhanceAutoConfiguration {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 注入增强的RocketMQEnhanceTemplate</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    public RocketMQEnhanceTemplate rocketMQEnhanceTemplate(RocketMQTemplate rocketMQTemplate){</span></span>
<span class="line"><span style="color:#A6ACCD;">        return new RocketMQEnhanceTemplate(rocketMQTemplate);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 解决RocketMQ Jackson不支持Java时间类型配置</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 源码参考：{@link org.apache.rocketmq.spring.autoconfigure.MessageConverterConfiguration}</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Primary</span></span>
<span class="line"><span style="color:#A6ACCD;">    public RocketMQMessageConverter enhanceRocketMQMessageConverter(){</span></span>
<span class="line"><span style="color:#A6ACCD;">        RocketMQMessageConverter converter = new RocketMQMessageConverter();</span></span>
<span class="line"><span style="color:#A6ACCD;">        CompositeMessageConverter compositeMessageConverter = (CompositeMessageConverter) converter.getMessageConverter();</span></span>
<span class="line"><span style="color:#A6ACCD;">        List&lt;MessageConverter&gt; messageConverterList = compositeMessageConverter.getConverters();</span></span>
<span class="line"><span style="color:#A6ACCD;">        for (MessageConverter messageConverter : messageConverterList) {</span></span>
<span class="line"><span style="color:#A6ACCD;">            if(messageConverter instanceof MappingJackson2MessageConverter){</span></span>
<span class="line"><span style="color:#A6ACCD;">                MappingJackson2MessageConverter jackson2MessageConverter = (MappingJackson2MessageConverter) messageConverter;</span></span>
<span class="line"><span style="color:#A6ACCD;">                ObjectMapper objectMapper = jackson2MessageConverter.getObjectMapper();</span></span>
<span class="line"><span style="color:#A6ACCD;">                objectMapper.registerModules(new JavaTimeModule());</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        return converter;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 环境隔离配置</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Bean</span></span>
<span class="line"><span style="color:#A6ACCD;">    @ConditionalOnProperty(name=&quot;rocketmq.enhance.enabledIsolation&quot;, havingValue=&quot;true&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">    public EnvironmentIsolationConfig environmentSetup(RocketEnhanceProperties rocketEnhanceProperties){</span></span>
<span class="line"><span style="color:#A6ACCD;">        return new EnvironmentIsolationConfig(rocketEnhanceProperties);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">public class EnvironmentIsolationConfig implements BeanPostProcessor {</span></span>
<span class="line"><span style="color:#A6ACCD;">    private RocketEnhanceProperties rocketEnhanceProperties;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    public EnvironmentIsolationConfig(RocketEnhanceProperties rocketEnhanceProperties) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        this.rocketEnhanceProperties = rocketEnhanceProperties;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 在装载Bean之前实现参数修改</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {</span></span>
<span class="line"><span style="color:#A6ACCD;">        if(bean instanceof DefaultRocketMQListenerContainer){</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">            DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">            if(rocketEnhanceProperties.isEnabledIsolation() &amp;&amp; StringUtils.hasText(rocketEnhanceProperties.getEnvironment())){</span></span>
<span class="line"><span style="color:#A6ACCD;">                container.setTopic(String.join(&quot;_&quot;, container.getTopic(),rocketEnhanceProperties.getEnvironment()));</span></span>
<span class="line"><span style="color:#A6ACCD;">            }</span></span>
<span class="line"><span style="color:#A6ACCD;">            return container;</span></span>
<span class="line"><span style="color:#A6ACCD;">        }</span></span>
<span class="line"><span style="color:#A6ACCD;">        return bean;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">@ConfigurationProperties(prefix = &quot;rocketmq.enhance&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">@Data</span></span>
<span class="line"><span style="color:#A6ACCD;">public class RocketEnhanceProperties {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private boolean enabledIsolation;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private String environment;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><h3 id="封装后的使用" tabindex="-1">封装后的使用 <a class="header-anchor" href="#封装后的使用" aria-label="Permalink to &quot;封装后的使用&quot;">​</a></h3><h4 id="引入依赖" tabindex="-1">引入依赖 <a class="header-anchor" href="#引入依赖" aria-label="Permalink to &quot;引入依赖&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">&lt;dependency&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">   &lt;groupId&gt;com.jianzh5&lt;/groupId&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">   &lt;artifactId&gt;cloud-rocket-starter&lt;/artifactId&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">&lt;/dependency&gt;</span></span></code></pre></div><h4 id="自定义配置" tabindex="-1">自定义配置 <a class="header-anchor" href="#自定义配置" aria-label="Permalink to &quot;自定义配置&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">rocketmq:</span></span>
<span class="line"><span style="color:#A6ACCD;"> ...</span></span>
<span class="line"><span style="color:#A6ACCD;"> enhance:</span></span>
<span class="line"><span style="color:#A6ACCD;">  # 启动隔离，用于激活配置类EnvironmentIsolationConfig</span></span>
<span class="line"><span style="color:#A6ACCD;">   # 启动后会自动在topic上拼接激活的配置文件，达到自动隔离的效果</span></span>
<span class="line"><span style="color:#A6ACCD;">   enabledIsolation: true</span></span>
<span class="line"><span style="color:#A6ACCD;">    # 隔离环境名称，拼接到topic后，topic_dev，默认空字符串</span></span>
<span class="line"><span style="color:#A6ACCD;">    environment: dev</span></span></code></pre></div><h4 id="发送消息" tabindex="-1">发送消息 <a class="header-anchor" href="#发送消息" aria-label="Permalink to &quot;发送消息&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@RestController</span></span>
<span class="line"><span style="color:#A6ACCD;">@RequestMapping(&quot;enhance&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">@Slf4j</span></span>
<span class="line"><span style="color:#A6ACCD;">public class EnhanceProduceController {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    //注入增强后的模板，可以自动实现环境隔离，日志记录</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Setter(onMethod_ = @Autowired)</span></span>
<span class="line"><span style="color:#A6ACCD;">    private RocketMQEnhanceTemplate rocketMQEnhanceTemplate;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    private static final String topic = &quot;rocket_enhance&quot;;</span></span>
<span class="line"><span style="color:#A6ACCD;">    private static final String tag = &quot;member&quot;;</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 发送实体消息</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @GetMapping(&quot;/member&quot;)</span></span>
<span class="line"><span style="color:#A6ACCD;">    public SendResult member() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        String key = UUID.randomUUID().toString();</span></span>
<span class="line"><span style="color:#A6ACCD;">        MemberMessage message = new MemberMessage();</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 设置业务key</span></span>
<span class="line"><span style="color:#A6ACCD;">        message.setKey(key);</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 设置消息来源，便于查询</span></span>
<span class="line"><span style="color:#A6ACCD;">        message.setSource(&quot;MEMBER&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 业务消息内容</span></span>
<span class="line"><span style="color:#A6ACCD;">        message.setUserName(&quot;Java日知录&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">        message.setBirthday(LocalDate.now());</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">        return rocketMQEnhanceTemplate.send(topic, tag, message);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>注意这里使用的是封装后的模板工具类，一旦在配置文件中启动环境隔离，则生产者的消息也自动发送到隔离后的topic中。</p><h4 id="消费者-2" tabindex="-1">消费者 <a class="header-anchor" href="#消费者-2" aria-label="Permalink to &quot;消费者&quot;">​</a></h4><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">@Slf4j</span></span>
<span class="line"><span style="color:#A6ACCD;">@Component</span></span>
<span class="line"><span style="color:#A6ACCD;">@RocketMQMessageListener(</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumerGroup = &quot;enhance_consumer_group&quot;,</span></span>
<span class="line"><span style="color:#A6ACCD;">        topic = &quot;rocket_enhance&quot;,</span></span>
<span class="line"><span style="color:#A6ACCD;">        selectorExpression = &quot;*&quot;,</span></span>
<span class="line"><span style="color:#A6ACCD;">        consumeThreadMax = 5 //默认是64个线程并发消息，配置 consumeThreadMax 参数指定并发消费线程数，避免太大导致资源不够</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">public class EnhanceMemberMessageListener extends EnhanceMessageHandler&lt;MemberMessage&gt; implements RocketMQListener&lt;MemberMessage&gt; {</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected void handleMessage(MemberMessage message) throws Exception {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 此时这里才是最终的业务处理，代码只需要处理资源类关闭异常，其他的可以交给父类重试</span></span>
<span class="line"><span style="color:#A6ACCD;">        System.out.println(&quot;业务消息处理:&quot;+message.getUserName());</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected void handleMaxRetriesExceeded(MemberMessage message) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 当超过指定重试次数消息时此处方法会被调用</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 生产中可以进行回退或其他业务操作</span></span>
<span class="line"><span style="color:#A6ACCD;">        log.error(&quot;消息消费失败，请执行后续处理&quot;);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 是否执行重试机制</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected boolean isRetry() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        return true;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected boolean throwException() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 是否抛出异常，false搭配retry自行处理异常</span></span>
<span class="line"><span style="color:#A6ACCD;">        return false;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    protected boolean filter() {</span></span>
<span class="line"><span style="color:#A6ACCD;">        // 消息过滤</span></span>
<span class="line"><span style="color:#A6ACCD;">        return false;</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span>
<span class="line"><span style="color:#A6ACCD;">    /**</span></span>
<span class="line"><span style="color:#A6ACCD;">     * 监听消费消息，不需要执行业务处理，委派给父类做基础操作，父类做完基础操作后会调用子类的实际处理类型</span></span>
<span class="line"><span style="color:#A6ACCD;">     */</span></span>
<span class="line"><span style="color:#A6ACCD;">    @Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    public void onMessage(MemberMessage memberMessage) {</span></span>
<span class="line"><span style="color:#A6ACCD;">        super.dispatchMessage(memberMessage);</span></span>
<span class="line"><span style="color:#A6ACCD;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>为了方便消费者对RocketMQ中的消息进行处理，我们可以使用EnhanceMessageHandler来进行消息的处理和逻辑的处理。</p><p>消费者实现了RocketMQListener的同时，可以继承EnhanceMessageHandler来进行公共逻辑的处理，而核心业务逻辑需要自己实现<code>handleMessage</code>方法。 如果需要对消息进行过滤或者去重的处理，则可以重写父类的filter方法进行实现。这样可以更加方便地对消息进行处理，减轻开发者的工作量。</p><p>以上，就是今天的主要内容，希望对你有所帮助！</p></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-37656e44><!--[--><!--]--><!----><nav class="prev-next" data-v-37656e44><div class="pager" data-v-37656e44><!----></div><div class="pager" data-v-37656e44><a class="pager-link next" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-37656e44><span class="desc" data-v-37656e44>Next page</span><span class="title" data-v-37656e44>Java基础</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"2、数据库_mysql_mysql面试_基础.md\":\"40da680a\",\"1、学前端_5、小程序_小程序项目.md\":\"60a1629b\",\"1、学前端_4、node_知识篇.md\":\"a7fb500e\",\"1、学前端_2、js_ts_es6 进阶.md\":\"6d07ba10\",\"1、学前端_3、vue_vue3_vue3进阶.md\":\"7ac622b4\",\"5、运维_jenkins.md\":\"929081f8\",\"1、学前端_2、js_ts_typescript.md\":\"875a4aa4\",\"2、数据库_mysql_mysql核心_设计.md\":\"7faf46d1\",\"2、数据库_mysql_mysql核心_基础.md\":\"d8e97f3e\",\"1、学前端_1、html_css_html基础.md\":\"7584d076\",\"1、学前端_5、专题篇_问题篇.md\":\"e893aaa2\",\"2、数据库_mysql_mysql面试_进阶.md\":\"f934806d\",\"3、springboot_运维_原理.md\":\"f4a39db6\",\"2、数据库_influxdb.md\":\"6e1711e1\",\"3、springboot_新特性.md\":\"cdf3e307\",\"mybatis_mybatisplus_jpa.md\":\"8e41681b\",\"1、学前端_5、小程序_小程序优化.md\":\"a2185198\",\"2、数据库_redis_redis基础.md\":\"856df0e0\",\"linux_实用脚本.md\":\"f2299dd5\",\"4、微服务_必备_分布式基础.md\":\"d49863d5\",\"2、数据库_redis_redis优化.md\":\"e66ae32f\",\"4、微服务_springsecurity_进阶篇.md\":\"235a8e9e\",\"5、运维_chatgpt.md\":\"10db3823\",\"2、数据库_mysql_分库分表.md\":\"e1c8a095\",\"start.md\":\"9bc1ff8d\",\"5、运维_github.md\":\"2ec6c735\",\"java学前端_css.md\":\"f11b47f0\",\"1、学前端_5、专题篇_知识篇.md\":\"a463ed8d\",\"linux_软件部署.md\":\"d6722925\",\"2、数据库_neo4j.md\":\"97ad22ac\",\"team.md\":\"ce467a6a\",\"nginx_实战篇.md\":\"7785486e\",\"index.md\":\"8c3ec167\",\"计算机基础_计算机网络_网络基础.md\":\"7a54a85d\",\"1、学前端_4、node_进阶篇.md\":\"60f6db69\",\"java_java集合.md\":\"a049b313\",\"1、学前端_3、vue_vue3_vue3高级.md\":\"614d1516\",\"1、学前端_5、小程序_微信小程序.md\":\"9a4be771\",\"5、运维_netty.md\":\"12ca0278\",\"2、数据库_mysql_mysql核心_运维.md\":\"83f97c16\",\"idea_vs code.md\":\"afdcb593\",\"java学前端_vue3_组件.md\":\"1086884e\",\"idea_chrome.md\":\"4a32afbc\",\"云原生_k8s.md\":\"db58e65a\",\"2、数据库_mysql_mysql核心_进阶.md\":\"61d16dff\",\"ssm_springbatch.md\":\"f799ab4a\",\"三高_分布式.md\":\"db1b8a1b\",\"2、数据库_elasticsearch_1、es基础.md\":\"04d17448\",\"linux_linux基础.md\":\"4b0bf394\",\"idea_idea插件.md\":\"fa86e45a\",\"可视化 _ 监控_可视化大屏.md\":\"004553bd\",\"2、数据库_mongodb_整合.md\":\"3c47d7f4\",\"4、微服务_springsecurity_基础篇.md\":\"534a3401\",\"4、微服务_进阶.md\":\"69095c58\",\"计算机基础_计算机基础_操作系统.md\":\"0f75d113\",\"可视化 _ 监控_zabbix.md\":\"71f2270e\",\"nginx_基础篇.md\":\"c7d8bb50\",\"1、学前端_4、node_项目实战.md\":\"bc5065b8\",\"2、数据库_redis_redis原理.md\":\"5cedf685\",\"可视化 _ 监控_监控基础.md\":\"ac56ce4d\",\"三高_高并发.md\":\"ea9ffc99\",\"2、数据库_redis_redis高级.md\":\"1d5872f6\",\"1、学前端_4、node_基础篇.md\":\"581cc13a\",\"2、数据库_mongodb_基础.md\":\"fb7a0a29\",\"idea_idea基础.md\":\"6f2f9638\",\"4、微服务_必备_sentinel.md\":\"2edfbf6c\",\"2、数据库_elasticsearch_3、es高级.md\":\"ef146606\",\"1、学前端_3、vue_vue3_vue3新语法.md\":\"8afd5409\",\"消息中间件_canal.md\":\"3949163c\",\"ssm_maven.md\":\"2c5e12ed\",\"4、微服务_springsecurity_高级篇.md\":\"882d3ff3\",\"linux_linux进阶.md\":\"188ef7b4\",\"计算机基础_设计模式_uml.md\":\"634ba256\",\"计算机基础_算法_leetcode.md\":\"77162fb9\",\"项目实战_小兔鲜_进阶篇1.md\":\"17c52c81\",\"1、学前端_2、js_ts_es6 基础.md\":\"fda3f18b\",\"项目实战_小兔鲜_进阶篇2.md\":\"a0f23006\",\"软件测试_测试基础.md\":\"8c1060cd\",\"2、数据库_redis_本地缓存.md\":\"00617fe6\",\"nginx_面试篇.md\":\"e3fb373a\",\"mybatis_mybatisplus_mybatis.md\":\"9239e0ad\",\"linux_shell.md\":\"ae53d83b\",\"2、数据库_mysql_mysql核心_优化.md\":\"36230425\",\"项目实战_项目推荐.md\":\"f9d97630\",\"mybatis_mybatisplus_mybatisplus.md\":\"0030fd35\",\"项目实战_百度地图_进阶篇.md\":\"c8b93267\",\"三高_高可用.md\":\"323840c5\",\"java_java新特性.md\":\"22abf56d\",\"软件测试_压力测试.md\":\"9ab44440\",\"java学前端_html_js.md\":\"e0fcd240\",\"2、数据库_redis_redis实战.md\":\"d6daeeab\",\"nginx_进阶篇.md\":\"e6b63195\",\"三高_秒杀.md\":\"3878bb64\",\"5、运维_git.md\":\"0264925c\",\"java_java进阶.md\":\"e79cb5b4\",\"并发 _ 多线程_基础篇.md\":\"7adbfac5\",\"项目实战_百度地图_基础篇.md\":\"8afa5954\",\"java学前端_react.md\":\"3ec827dd\",\"1、学前端_1、html_css_css基础.md\":\"01b56712\",\"项目实战_小兔鲜_基础篇.md\":\"646f5df5\",\"1、学前端_2、js_ts_js 基础.md\":\"cb13e36f\",\"可视化 _ 监控_监控进阶.md\":\"0cdbc292\",\"计算机基础_设计模式_基础篇.md\":\"51617287\",\"计算机基础_数据结构_基础篇.md\":\"b2bfd8d4\",\"项目实战_苍穹外卖_进阶篇.md\":\"48415e41\",\"ssm_spring.md\":\"ab514659\",\"消息中间件_rabbitmq.md\":\"45b1eb28\",\"1、学前端_1、html_css_网页进阶.md\":\"db998248\",\"消息中间件_kafka.md\":\"b747dabf\",\"云原生_docker.md\":\"983c7ba7\",\"4、微服务_必备_分布式锁.md\":\"5af1cf8d\",\"消息中间件_rocketmq.md\":\"d441da85\",\"项目实战_黑马头条_基础篇.md\":\"b05af3a6\",\"ssm_springmvc.md\":\"81b9714f\",\"项目实战_支付.md\":\"1d7407dd\",\"项目实战_黑马头条_进阶篇2.md\":\"bff0015b\",\"项目实战_黑马头条_进阶篇.md\":\"19f18388\",\"java学前端_vue2_组件.md\":\"58c6b1df\",\"3、springboot_基础篇.md\":\"529c66f4\",\"3、springboot_应用篇.md\":\"8b92aa61\",\"项目实战_黑马头条_高级篇.md\":\"227c08c1\",\"1、学前端_5、小程序_uniapp.md\":\"71a282b4\",\"项目实战_云尚办公_基础篇.md\":\"1fe188ba\",\"并发 _ 多线程_并发完善.md\":\"26619c46\",\"1、学前端_2、js_ts_js 进阶.md\":\"657dfb8f\",\"java_java高级.md\":\"23782d1a\",\"java_java基础.md\":\"86d67c77\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"VitePress\",\"description\":\"A VitePress site\",\"base\":\"/notebook/\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"algolia\":{\"appId\":\"DW7O63I9IR\",\"apiKey\":\"f8ed758cdb288a8b06542bc35923c1a1\",\"indexName\":\"notebook\"},\"sidebar\":[{\"text\":\"Java\",\"collapsed\":true,\"items\":[{\"text\":\"Java基础\",\"link\":\"/Java/Java基础\"},{\"text\":\"Java新特性\",\"link\":\"/Java/Java新特性\"},{\"text\":\"Java进阶\",\"link\":\"/Java/Java进阶\"},{\"text\":\"Java集合\",\"link\":\"/Java/Java集合\"},{\"text\":\"Java高级\",\"link\":\"/Java/Java高级\"}]},{\"text\":\"Linux\",\"collapsed\":true,\"items\":[{\"text\":\"Linux基础\",\"link\":\"/Linux/Linux基础\"},{\"text\":\"Linux新特性\",\"link\":\"/Linux/Linux进阶\"},{\"text\":\"Shell脚本\",\"link\":\"/Linux/Shell\"},{\"text\":\"实用脚本\",\"link\":\"/Linux/实用脚本\"},{\"text\":\"软件部署\",\"link\":\"/Linux/软件部署\"}]},{\"text\":\"Nginx\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/Nginx/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/Nginx/进阶篇\"},{\"text\":\"实战篇\",\"link\":\"/Nginx/实战篇\"},{\"text\":\"面试篇\",\"link\":\"/Nginx/面试篇\"}]},{\"text\":\"SSM\",\"collapsed\":true,\"items\":[{\"text\":\"Maven\",\"link\":\"/SSM/Maven\"},{\"text\":\"Spring\",\"link\":\"/SSM/Spring\"},{\"text\":\"SpringMVC\",\"link\":\"/SSM/SpringMVC\"},{\"text\":\"SpringBatch\",\"link\":\"/SSM/SpringBatch\"}]},{\"text\":\"SpringBoot\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/3、SpringBoot/基础篇\"},{\"text\":\"应用篇\",\"link\":\"/3、SpringBoot/应用篇\"},{\"text\":\"新特性\",\"link\":\"/3、SpringBoot/新特性\"},{\"text\":\"运维&原理\",\"link\":\"/3、SpringBoot/运维&原理\"}]},{\"text\":\"SpringCloud\",\"collapsed\":true,\"items\":[{\"text\":\"SpringCloud\",\"link\":\"/4、微服务/进阶\"},{\"text\":\"Sentinel\",\"link\":\"/4、微服务/必备/Sentinel\"}]},{\"text\":\"SpringSecurity\",\"collapsed\":true,\"items\":[{\"text\":\"SpringSecurity基础篇\",\"link\":\"/4、微服务/SpringSecurity/基础篇\"},{\"text\":\"SpringSecurity进阶篇\",\"link\":\"/4、微服务/SpringSecurity/进阶篇\"},{\"text\":\"SpringSecurity高级篇\",\"link\":\"/4、微服务/SpringSecurity/高级篇\"}]},{\"text\":\"Mybatis & MybatisPlus\",\"collapsed\":true,\"items\":[{\"text\":\"Mybatis\",\"link\":\"/Mybatis&MybatisPlus/Mybatis\"},{\"text\":\"MybatisPlus\",\"link\":\"/Mybatis&MybatisPlus/MybatisPlus\"},{\"text\":\"JPA\",\"link\":\"/Mybatis&MybatisPlus/JPA\"}]},{\"text\":\"Git & ChatGPT\",\"collapsed\":true,\"items\":[{\"text\":\"Git\",\"link\":\"/5、运维/Git\"},{\"text\":\"Github\",\"link\":\"/5、运维/Github\"},{\"text\":\"ChatGPT\",\"link\":\"/5、运维/ChatGPT\"},{\"text\":\"Jenkins\",\"link\":\"/5、运维/Jenkins\"},{\"text\":\"Netty\",\"link\":\"/5、运维/Netty\"}]},{\"text\":\"数据库\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL基础\",\"link\":\"/2、数据库/MySQL/MySQL核心/基础\"},{\"text\":\"MySQL进阶\",\"link\":\"/2、数据库/MySQL/MySQL核心/进阶\"},{\"text\":\"MySQL优化\",\"link\":\"/2、数据库/MySQL/MySQL核心/优化\"},{\"text\":\"MySQL设计\",\"link\":\"/2、数据库/MySQL/MySQL核心/设计\"},{\"text\":\"MySQL运维\",\"link\":\"/2、数据库/MySQL/MySQL核心/运维\"},{\"text\":\"分库分表\",\"link\":\"/2、数据库/MySQL/分库分表\"}]},{\"text\":\"Redis\",\"collapsed\":true,\"items\":[{\"text\":\"Redis基础\",\"link\":\"/2、数据库/Redis/Redis基础\"},{\"text\":\"Redis优化\",\"link\":\"/2、数据库/Redis/Redis优化\"},{\"text\":\"Redis原理\",\"link\":\"/2、数据库/Redis/Redis原理\"},{\"text\":\"Redis高级\",\"link\":\"/2、数据库/Redis/Redis高级\"},{\"text\":\"Redis实战\",\"link\":\"/2、数据库/Redis/Redis实战\"},{\"text\":\"本地缓存\",\"link\":\"/2、数据库/Redis/本地缓存\"}]},{\"text\":\"MongoDB\",\"collapsed\":true,\"items\":[{\"text\":\"MongoDB基础\",\"link\":\"/2、数据库/MongoDB/基础\"},{\"text\":\"MongoDB进阶\",\"link\":\"/2、数据库/MongoDB/整合\"}]},{\"text\":\"ElasticSearch\",\"collapsed\":true,\"items\":[{\"text\":\"ES基础\",\"link\":\"/2、数据库/ElasticSearch/1、ES基础\"},{\"text\":\"ES高级\",\"link\":\"/2、数据库/ElasticSearch/3、ES高级\"}]},{\"text\":\"InfluxDB\",\"link\":\"/2、数据库/influxdb\"},{\"text\":\"Neo4j\",\"link\":\"/2、数据库/Neo4j\"}]},{\"text\":\"高并发 & 秒杀 & 分布式\",\"collapsed\":true,\"items\":[{\"text\":\"分布式理论\",\"link\":\"/三高/分布式\"},{\"text\":\"分布式锁\",\"link\":\"/4、微服务/必备/分布式锁\"},{\"text\":\"秒杀\",\"link\":\"/三高/秒杀\"},{\"text\":\"高可用\",\"link\":\"/三高/高可用\"},{\"text\":\"高并发\",\"link\":\"/三高/高并发\"}]},{\"text\":\"云原生\",\"collapsed\":true,\"items\":[{\"text\":\"Docker\",\"link\":\"/云原生/Docker\"},{\"text\":\"K8S\",\"link\":\"/云原生/K8S\"}]},{\"text\":\"可视化 & 监控\",\"collapsed\":true,\"items\":[{\"text\":\"监控基础\",\"link\":\"/可视化 & 监控/监控基础\"},{\"text\":\"监控进阶\",\"link\":\"/可视化 & 监控/监控进阶\"},{\"text\":\"可视化大屏\",\"link\":\"/可视化 & 监控/可视化大屏\"},{\"text\":\"Zabbix\",\"link\":\"/可视化 & 监控/Zabbix\"}]},{\"text\":\"学前端\",\"collapsed\":true,\"items\":[{\"text\":\"HTML+CSS\",\"collapsed\":true,\"items\":[{\"text\":\"HTML基础\",\"link\":\"/1、学前端/1、HTML+CSS/HTML基础\"},{\"text\":\"CSS基础\",\"link\":\"/1、学前端/1、HTML+CSS/CSS基础\"},{\"text\":\"网页进阶\",\"link\":\"/1、学前端/1、HTML+CSS/网页进阶\"}]},{\"text\":\"JS+TS\",\"collapsed\":true,\"items\":[{\"text\":\"JS基础\",\"link\":\"/1、学前端/2、JS+TS/JS 基础\"},{\"text\":\"JS进阶\",\"link\":\"/1、学前端/2、JS+TS/JS 进阶\"},{\"text\":\"ES6基础\",\"link\":\"/1、学前端/2、JS+TS/ES6 基础\"},{\"text\":\"ES6进阶\",\"link\":\"/1、学前端/2、JS+TS/ES6 进阶\"},{\"text\":\"TS基础\",\"link\":\"/1、学前端/2、JS+TS/TypeScript\"}]},{\"text\":\"NodeJS\",\"collapsed\":true,\"items\":[{\"text\":\"Node基础\",\"link\":\"/1、学前端/4、Node/基础篇\"},{\"text\":\"Node进阶\",\"link\":\"/1、学前端/4、Node/进阶篇\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/4、Node/项目实战\"}]},{\"text\":\"Vue\",\"collapsed\":true,\"items\":[{\"text\":\"Vue3进阶\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3进阶\"},{\"text\":\"Vue3高级\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3高级\"},{\"text\":\"Vue3新语法\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3新语法\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/3、Vue/Vue2/Vue2项目\"}]},{\"text\":\"小程序\",\"collapsed\":true,\"items\":[{\"text\":\"小程序基础\",\"link\":\"/1、学前端/5、小程序/微信小程序\"},{\"text\":\"小程序优化\",\"link\":\"/1、学前端/5、小程序/小程序优化\"},{\"text\":\"uniapp\",\"link\":\"/1、学前端/5、小程序/uniapp\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/5、小程序/小程序项目\"}]}]},{\"text\":\"计算机基础\",\"collapsed\":true,\"items\":[{\"text\":\"数据结构\",\"link\":\"/计算机基础/数据结构/基础篇\"},{\"text\":\"操作系统\",\"link\":\"/计算机基础/计算机基础/操作系统\"},{\"text\":\"设计模式\",\"link\":\"/计算机基础/设计模式/基础篇\"},{\"text\":\"计算机网络\",\"link\":\"/计算机基础/计算机网络/网络基础\"},{\"text\":\"UML\",\"link\":\"/计算机基础/设计模式/UML\"},{\"text\":\"LeetCode\",\"link\":\"/计算机基础/算法/LeetCode\"}]},{\"text\":\"项目实战\",\"collapsed\":true,\"items\":[{\"text\":\"云尚办公\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/云尚办公/基础篇\"}]},{\"text\":\"小兔鲜\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/小兔鲜/基础篇\"},{\"text\":\"进阶篇1\",\"link\":\"/项目实战/小兔鲜/进阶篇1\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/小兔鲜/进阶篇2\"}]},{\"text\":\"地图\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/百度地图/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/百度地图/进阶篇\"}]},{\"text\":\"苍穹外卖\",\"collapsed\":true,\"items\":[{\"text\":\"进阶篇\",\"link\":\"/项目实战/苍穹外卖/进阶篇\"}]},{\"text\":\"黑马头条\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/黑马头条/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/黑马头条/进阶篇\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/黑马头条/进阶篇2\"},{\"text\":\"高级篇\",\"link\":\"/项目实战/黑马头条/高级篇\"}]},{\"text\":\"支付\",\"link\":\"/项目实战/支付\"},{\"text\":\"项目推荐\",\"link\":\"/项目实战/项目推荐\"}]},{\"text\":\"团队成员\",\"link\":\"/team\"}],\"siteTitle\":\"任硕的文档\",\"logo\":\"/Vue.png\",\"nav\":[{\"text\":\"Java学前端\",\"items\":[{\"items\":[{\"text\":\"HTML+JS\",\"link\":\"/Java学前端/HTML+JS\"},{\"text\":\"CSS\",\"link\":\"/Java学前端/CSS\"},{\"text\":\"Vue2+组件\",\"link\":\"/Java学前端/Vue2+组件\"},{\"text\":\"Vue3+组件\",\"link\":\"/Java学前端/Vue3+组件\"},{\"text\":\"React\",\"link\":\"/Java学前端/React\"}]}],\"activeMatch\":\"/Java/\"},{\"text\":\"软件测试\",\"items\":[{\"items\":[{\"text\":\"测试基础\",\"link\":\"/软件测试/测试基础\"},{\"text\":\"压力测试\",\"link\":\"/软件测试/压力测试\"}]}]},{\"text\":\"多线程\",\"items\":[{\"items\":[{\"text\":\"基础篇\",\"link\":\"/并发 & 多线程/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/并发 & 多线程/并发完善\"}]}]},{\"text\":\"开发工具\",\"items\":[{\"items\":[{\"text\":\"Chrome\",\"link\":\"/IDEA/Chrome\"},{\"text\":\"IDEA基础\",\"link\":\"/IDEA/IDEA基础\"},{\"text\":\"IDEA插件\",\"link\":\"/IDEA/IDEA插件\"},{\"text\":\"VS Code\",\"link\":\"/IDEA/VS Code\"}]}]},{\"text\":\"消息中间件\",\"items\":[{\"items\":[{\"text\":\"RabbitMQ\",\"link\":\"/消息中间件/RabbitMQ\"},{\"text\":\"RocketMQ\",\"link\":\"/消息中间件/RocketMQ\"},{\"text\":\"Kafka\",\"link\":\"/消息中间件/Kafka\"},{\"text\":\"Canal\",\"link\":\"/消息中间件/Canal\"}]}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/renshuo123/renshuo123.github.io\"},{\"icon\":\"twitter\",\"link\":\"#\"},{\"icon\":{\"svg\":\"<svg t=\\\"1676028692954\\\" class=\\\"icon\\\" ...</path></svg>\"},\"link\":\"https://github.com/\"}]},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>